﻿using System.Windows;
using System.Windows.Input;
using System.IO;
using System.Collections.Generic;
using Microsoft.Win32;
using ICSharpCode.AvalonEdit.Highlighting;
using System.Text;
using System;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Documents;
using ICSharpCode.AvalonEdit.Utils;
using System.Windows.Media.Imaging;
using System.Xml;
using System.Windows.Markup;
using MahApps.Metro.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using ICSharpCode.AvalonEdit.Document;
using System.Media;
using System.Text.RegularExpressions;

namespace LunarSF.SHomeWorkshop.LunarMarkdownEditor
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : MetroWindow
    {
        /// <summary>
        /// Html Help Workshop安装路径。
        /// </summary>
        private string hhwInstalledPath = Globals.hhwInstalledPath;
        /// <summary>
        /// Html Help Workshop安装路径。
        /// </summary>
        public string HHWInstalledPath { get { return this.hhwInstalledPath; } }

        /// <summary>
        /// [构造方法]，初始化主窗口。
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();

            try
            {
                this.mainTabControl.SelectionChanged += MainTabControl_SelectionChanged;

                //提供托盘图标
                CreateNoticeIcon();
                //保证窗体显示在上方。
                windowState = WindowState;

                helpFrame.Source = new Uri(Globals.PathOfWorkspace + "Help~.html");//不能加这个前缀"file:///" +

                //载入之前指定的各个历史工作区目录
                if (File.Exists(Globals.PathOfHistoryWorkspaceFileFullName))
                {
                    string[] workspaces = File.ReadAllLines(Globals.PathOfHistoryWorkspaceFileFullName);
                    if (workspaces.Length > 0)
                    {
                        for (int i = 0; i < workspaces.Length; i++)
                        {
                            if (string.IsNullOrEmpty(workspaces[i]) == false)
                            {
                                var rw = new RecentDirectoryListBoxItem(workspaces[i])
                                {
                                    ToolTip = "双击设置为当前工作区目录",
                                };
                                rw.MouseDoubleClick += rw_MouseDoubleClick;
                                lbxHistoryWorkspaces.Items.Add(rw);
                            }
                        }
                    }
                }

                RefreshWorkspaceHistoryList();

                //载入之前指定的各个历史导出目录
                if (File.Exists(Globals.PathOfHistoryOutputFileFullName))
                {
                    string[] outports = File.ReadAllLines(Globals.PathOfHistoryOutputFileFullName);
                    if (outports.Length > 0)
                    {
                        for (int i = 0; i < outports.Length; i++)
                        {
                            if (string.IsNullOrEmpty(outports[i]) == false)
                            {
                                var row = new RecentDirectoryListBoxItem(outports[i])
                                {
                                    ToolTip = "双击将编译的所有 Html 文件及资源文件导出到该目录",
                                };
                                row.MouseDoubleClick += row_MouseDoubleClick;
                                lbxHistoryOutport.Items.Add(row);
                            }
                        }
                    }
                }

                RefreshOutputHistoryList();

                this.configManager = new XmlConfigManager(Globals.PathOfUserFolder + "config.xml");

                //载入指定的工作区目录
                string workspace = this.configManager.Get("Workspace");
                if (string.IsNullOrEmpty(workspace) == false && Directory.Exists(workspace))
                {
                    if (workspace.EndsWith("\\") == false) workspace += "\\";
                    if (Directory.Exists(workspace))
                    {
                        Globals.PathOfWorkspace = workspace;
                    }

                    //检查必须的文件是否存在。
                    CopyCssAndResourceFiles(workspace);
                }

                //检查工作区目录是否存在。
                if (Directory.Exists(Globals.PathOfWorkspace) == false)
                {
                    try
                    {
                        Directory.CreateDirectory(Globals.PathOfWorkspace);
                        CopyCssAndResourceFiles(Globals.PathOfWorkspace);
                    }
                    catch (System.Exception ex)
                    {
                        LMessageBox.Show("　　未能在创建工作区目录。这可能是因为Windows系统权限造成的。一般来说，不应将工作区指定在系统目录下。异常消息如下：\r\n" +
                            ex.Message + "\r\n" + ex.StackTrace,
                            Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    }
                }
                else
                {
                    if (Globals.PathOfWorkspace.ToLower() != Globals.DefaultWorkspacePath.ToLower())
                    {
                        //检查必须的文件是否存在。
                        CopyCssAndResourceFiles(Globals.PathOfWorkspace);
                    }
                }

                tbxPathOfWorkspace.Text = Globals.PathOfWorkspace;

                #region 快捷键事件处理

                //注意，某些快捷键是全局的，因此可以放在主窗口事件中处理；
                //例如增加字号、减小字号、保存文件、全部保存、新建文件、打开文件、关闭窗口（不需要处理）、生成HTML、生成填空文本、帮助文档……
                //而另一些快捷键必须放到TabControl的事件中处理。
                //这些命令使用的快捷键与Explorer使用的快捷键会产生冲突，此时必须在TabControl中实现。
                //包括：Ctrl+X,Ctrl+C,Ctrl+V,Ctrl+A,Ctrl+Z,Ctrl+Y,Del

                //*** 不清楚是不是会产生冲突的快捷键，为防止误操作，一律在主窗口的键盘事件中进行处理

                this.PreviewKeyDown += MainWindow_PreviewKeyDown;
                mainTabControl.PreviewKeyDown += mainTabControl_PreviewKeyDown;

                #endregion

                this.Closing += MainWindow_Closing;

                this.workspaceManager = new WorkspaceManager(this);
                this.workspaceManager.Refresh(null);

                mainTabControl.SelectionChanged += mainTabControl_SelectionChanged;

                this.Loaded += MainWindow_Loaded;

                //载入字体列表
                InitializeFontFamilyList();

                //载入默认字体
                this.defaultFontFamily = mainTabControl.FontFamily;
                var defFontFamilyEnName = this.configManager.Get("FontFamily");
                foreach (var item in fontFamilyList.Items)
                {
                    FontFamilyListItem fli = item as FontFamilyListItem;
                    if (fli == null) continue;

                    string enName;
                    if (fli.FontFamily.FamilyNames.TryGetValue(System.Windows.Markup.XmlLanguage.GetLanguage("en-us"), out enName))
                    {
                        if (defFontFamilyEnName == enName)
                        {
                            this.mainTabControl.FontFamily = fli.FontFamily;
                            this.fontFamilyList.SelectedItem = fli;
                            break;
                        }
                    }
                }

                this.fontFamilyList.SelectionChanged += fontFamilyList_SelectionChanged;

                //载入格式化动作何时执行的设置
                var format = this.configManager.Get("AutoFormat");
                if (string.IsNullOrEmpty(format) == false && (format.ToLower() == "true"))
                    miFormatBeforeSave.IsChecked = true;
                else
                    miFormatBeforeSave.IsChecked = false;

                //treeview items 变色
                tvWorkDirectory.SelectedItemChanged += TvWorkDirectory_SelectedItemChanged;
                tvFindAndReplace.SelectedItemChanged += TvFind_SelectedItemChanged;
                tvTaskList.SelectedItemChanged += TvFind_SelectedItemChanged;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
            }
        }

        private void TvFind_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            IFindItem oldItem = (e.OldValue as IFindItem);
            if (oldItem != null)
            {
                oldItem.HeaderTextBlock.Foreground = oldItem.ForegroundOfText;
                oldItem.BorderBrush = Brushes.Transparent;
            }

            var newItem = (e.NewValue as IFindItem);
            if (newItem != null)
            {
                newItem.HeaderTextBlock.Foreground = Brushes.Black;
                newItem.BorderBrush = Brushes.Blue;
            }
        }

        /// <summary>
        /// 工作区管理器中各条目变色功能。
        /// </summary>
        private void TvWorkDirectory_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            var oldItem = (e.OldValue as WorkspaceTreeViewItem);
            if (oldItem != null)
            {
                oldItem.HeaderTextBlock.Foreground = oldItem.ForegroundOfText;
                oldItem.BorderBrush = Brushes.Transparent;
            }

            var newItem = (e.NewValue as WorkspaceTreeViewItem);
            if (newItem != null)
            {
                newItem.HeaderTextBlock.Foreground = Brushes.Black;
                newItem.BorderBrush = Brushes.Blue;
            }
        }

        /// <summary>
        /// 所有文档都关闭后，自动退出全屏状态。
        /// </summary>
        private void MainTabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (this.mainTabControl.Items.Count <= 0)
            {
                if (mainMenu.Visibility == Visibility.Collapsed)
                {
                    SwitchFullScreen(false);
                }
            }

            var activeEdit = this.ActivedEditor;
            if (activeEdit != null)
            {
                var destWorkspaceTreeViewItem = FindWorkspaceTreeViewItem(activeEdit.FullFilePath);
                if (destWorkspaceTreeViewItem != null)
                {
                    destWorkspaceTreeViewItem.IsSelected = true;
                }
            }
        }

        #region 字体相关

        private FontFamily defaultFontFamily;

        public FontFamily DefaultFontFamily
        {
            get { return defaultFontFamily; }
            set { defaultFontFamily = value; }
        }

        private bool _familyListValid;
        private ICollection<FontFamily> _familyCollection;

        public ICollection<FontFamily> FontFamilyCollection
        {
            get
            {
                return (_familyCollection == null) ? Fonts.SystemFontFamilies : _familyCollection;
            }

            set
            {
                if (value != _familyCollection)
                {
                    _familyCollection = value;
                    InvalidateFontFamilyList();
                }
            }
        }

        private void InvalidateFontFamilyList()
        {
            if (_familyListValid)
            {
                fontFamilyList.Items.Clear();
                _familyListValid = false;
            }
        }

        private void InitializeFontFamilyList()
        {
            ICollection<FontFamily> familyCollection = FontFamilyCollection;
            if (familyCollection != null)
            {
                FontFamilyListItem[] items = new FontFamilyListItem[familyCollection.Count];

                int i = 0;

                foreach (FontFamily family in familyCollection)
                {
                    items[i++] = new FontFamilyListItem(family);
                }

                Array.Sort<FontFamilyListItem>(items);

                foreach (FontFamilyListItem item in items)
                {
                    fontFamilyList.Items.Add(item);
                }
            }
        }

        #endregion

        /// <summary>
        /// 双击“最近工作区列表”中的某个条目，将工作区切换到该条目记录的目录。
        /// </summary>
        void rw_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            ChangeWorkspace((sender as RecentDirectoryListBoxItem).DirectoryPath);
        }

        /// <summary>
        /// 复制特定图像资源文件到指定目录中去。
        /// </summary>
        /// <param name="destImageFolder">目标位置。</param>
        /// <param name="fileShortName">图像文件短名。</param>
        private static void CopyImageFile(string destImageFolder, string fileShortName)
        {
            var destIamgeFilePath = destImageFolder + fileShortName;
            var srcImageFilePath = Globals.DefaultWorkspacePath + $"Images~\\{fileShortName}";
            if (File.Exists(srcImageFilePath))
            {
                if (File.Exists(destIamgeFilePath))
                {
                    FileInfo srcIamgeFileInfo = new FileInfo(srcImageFilePath);
                    FileInfo destImageFileInfo = new FileInfo(destIamgeFilePath);
                    if (destImageFileInfo.LastWriteTime.CompareTo(srcIamgeFileInfo.LastWriteTime) < 0)
                    {
                        File.Copy(srcImageFilePath, destIamgeFilePath, true);
                    }
                }
                else
                {
                    File.Copy(srcImageFilePath, destIamgeFilePath, true);
                }
            }
        }

        /// <summary>
        /// 将编译后的 Html 文件需要的资源文件复制到目标工作区目录中去。
        /// </summary>
        /// <param name="workspace">目标工作区目录路径。</param>
        private static void CopyCssAndResourceFiles(string workspace)
        {
            if (string.IsNullOrEmpty(workspace)) return;

            if (workspace.EndsWith("\\") == false) workspace += "\\";

            if (workspace == Globals.DefaultWorkspacePath) return;//不需要复制。

            if (Directory.Exists(workspace) == false)
            {
                Directory.CreateDirectory(workspace);
            }

            var destImageFolder = workspace + "Images~\\";
            if (Directory.Exists(destImageFolder) == false)
            {
                Directory.CreateDirectory(destImageFolder);
            }

            try
            {
                CopyImageFile(destImageFolder, "header_icon_dark.png");
                CopyImageFile(destImageFolder, "header_icon_light.png");
                CopyImageFile(destImageFolder, "comment_dark.png");
                CopyImageFile(destImageFolder, "comment_light.png");

                CopyImageFile(destImageFolder, "region_e_dark.png");
                CopyImageFile(destImageFolder, "region_e_light.png");
                CopyImageFile(destImageFolder, "region_i_dark.png");
                CopyImageFile(destImageFolder, "region_i_light.png");
                CopyImageFile(destImageFolder, "region_q_dark.png");
                CopyImageFile(destImageFolder, "region_q_light.png");
                CopyImageFile(destImageFolder, "region_w_dark.png");
                CopyImageFile(destImageFolder, "region_w_light.png");

                //jQuery 2.0以上版本对IE的版本有要求，不适合本程序使用。
                var destJQuery = workspace + "jquery-1.7.0.min.js";
                if (File.Exists(destJQuery) == false)
                {
                    File.Copy(Globals.DefaultWorkspacePath + "jquery-1.7.0.min.js", destJQuery);
                }

                var destLessonDarkCSS = workspace + "lesson_dark.css";
                var srcLessonDarkCSS = Globals.DefaultWorkspacePath + "lesson_dark.css";
                if (File.Exists(srcLessonDarkCSS))
                {
                    if (File.Exists(destLessonDarkCSS))
                    {
                        FileInfo srcLessonDarkCSSFileInfo = new FileInfo(srcLessonDarkCSS);
                        FileInfo destLessonDarkCSSFileInfo = new FileInfo(destLessonDarkCSS);
                        if (destLessonDarkCSSFileInfo.LastWriteTime.CompareTo(srcLessonDarkCSSFileInfo.LastWriteTime) < 0)
                        {
                            File.Copy(srcLessonDarkCSS, destLessonDarkCSS, true);
                        }
                    }
                    else
                    {
                        File.Copy(srcLessonDarkCSS, destLessonDarkCSS, true);
                    }
                }

                var destLessonLightCSS = workspace + "lesson_light.css";
                var srcLessonLightCSS = Globals.DefaultWorkspacePath + "lesson_light.css";
                if (File.Exists(srcLessonLightCSS))
                {
                    if (File.Exists(destLessonLightCSS))
                    {
                        FileInfo destLessonLightCSSFileInfo = new FileInfo(destLessonLightCSS);
                        FileInfo srcLessonLightCSSFileInfo = new FileInfo(srcLessonLightCSS);
                        if (destLessonLightCSSFileInfo.LastWriteTime.CompareTo(srcLessonLightCSSFileInfo.LastWriteTime) < 0)
                        {
                            File.Copy(srcLessonLightCSS, destLessonLightCSS, true);
                        }
                    }
                    else
                    {
                        File.Copy(srcLessonLightCSS, destLessonLightCSS, true);
                    }
                }

                var destHelpHtml = workspace + "Help~.html";
                var srcHelpHtml = Globals.DefaultWorkspacePath + "Help~.html";
                if (File.Exists(srcHelpHtml))
                {
                    if (File.Exists(destHelpHtml))
                    {
                        FileInfo srcHelpHtmlFileInfo = new FileInfo(srcHelpHtml);
                        FileInfo destHelpHtmlFileInfo = new FileInfo(destHelpHtml);
                        if (destHelpHtmlFileInfo.LastWriteTime.CompareTo(srcHelpHtmlFileInfo.LastWriteTime) < 0)
                        {
                            File.Copy(srcHelpHtml, destHelpHtml, true);
                        }
                    }
                    else
                    {
                        File.Copy(srcHelpHtml, destHelpHtml, true);
                    }
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
            }
        }

        /// <summary>
        /// 在主窗口载入后进行一些初始化操作。
        /// </summary>
        void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            try
            {
                //启动时全屏
                var startFullScreen = this.configManager.Get("StartFullScreen");
                if (string.IsNullOrEmpty(startFullScreen) == false)
                {
                    bool sf;
                    if (bool.TryParse(startFullScreen, out sf))
                    {
                        this.StartFullScreen = sf;

                        if (sf) SwitchFullScreen(true);
                    }
                }
                miStartFullScreen.IsChecked = this.StartFullScreen;

                helpFrame.Source = new Uri(Globals.DefaultWorkspacePath + "Help~.html");//不能加此前缀"file:///" + 

                var s = configManager.Get("color");
                if (string.IsNullOrEmpty(s) == false)
                {
                    foreach (var i in cmbColor.Items)
                    {
                        ComboBoxItem cbi = i as ComboBoxItem;
                        if (cbi == null) continue;

                        if (cbi.Tag.ToString() == s)
                        {
                            cbi.IsSelected = true;
                            break;
                        }
                    }
                }
                cmbColor_SelectionChanged(sender, null);

                //读取Html Help Workshop.exe的路径。
                if (File.Exists(Globals.hhwInstalledPath) == false)
                {
                    //为什么要这样做呢？
                    //这是因为版权问题，不便集成Html Help Workshop到我的安装包中。
                    //但问题在于，从网上下载的各个版本（哪怕是微软官方网站下载的英文版），在独立状态下都很容易出错。
                    //所以还是集成了。这样，原先分离的做法就不想作用了。
                    //如果将来还可以分离，这段代码就又有用了。所以未删除。
                    var hhwInstalledPath = this.configManager.Get("HHWInstalledPath");
                    if (File.Exists(hhwInstalledPath))
                    {
                        this.hhwInstalledPath = hhwInstalledPath;
                    }
                    else
                    {
                        //尝试从注册表中读取安装的hhw.exe的路径
                        RegistryKey regSubKey;
                        RegistryKey regKey = Registry.LocalMachine;
                        string strRegPath = @"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\hhw.exe";
                        regSubKey = regKey.OpenSubKey(strRegPath);
                        if (regSubKey != null)
                        {
                            string pathValue = regSubKey.GetValue("").ToString();
                            if (File.Exists(pathValue))
                            {
                                this.hhwInstalledPath = pathValue;
                            }
                        }
                    }
                }

                var isExamEnabledText = this.configManager.Get("IsExamEnabled");
                if (string.IsNullOrEmpty(isExamEnabledText) == false)
                {
                    bool r;
                    if (bool.TryParse(isExamEnabledText, out r))
                    {
                        this.IsExamEnabled = r;
                    }
                }
                else
                {
                    this.IsExamEnabled = false;//默认关闭
                }
                //miIsExamEnabled.IsChecked = this.IsExamEnabled;

                var formatAfterCompileText = this.configManager.Get("FormatAfterCompile");
                if (string.IsNullOrEmpty(formatAfterCompileText) == false)
                {
                    bool fac;
                    if (bool.TryParse(formatAfterCompileText, out fac))
                    {
                        this.FormatAfterCompile = fac;
                    }
                }
                else
                {
                    this.FormatAfterCompile = false;//默认关闭
                }
                miFormatAfterCompile.IsChecked = this.FormatAfterCompile;

                var isAutoCompletionEnabledText = this.configManager.Get("IsAutoCompletionEnabled");
                if (string.IsNullOrEmpty(isAutoCompletionEnabledText) == false)
                {
                    bool ac;
                    if (bool.TryParse(isAutoCompletionEnabledText, out ac))
                    {
                        this.IsAutoCompletionEnabled = ac;
                    }
                }
                else
                {
                    this.IsAutoCompletionEnabled = false;//默认关闭，这玩意儿还是很麻烦的。
                }
                miIsAutoCompletionEnabled.IsChecked = this.IsAutoCompletionEnabled;

                var isEnToChineseDictEnabledText = this.configManager.Get("IsEnToChineseDictEnabled");
                if (string.IsNullOrEmpty(isEnToChineseDictEnabledText) == false)
                {
                    bool ecde;
                    if (bool.TryParse(isEnToChineseDictEnabledText, out ecde))
                    {
                        this.IsEnToChineseDictEnabled = ecde;
                    }
                }
                else
                {
                    this.isEnToChineseDictEnabled = false;//默认关闭
                }
                miIsEnToChineseDictEnabled.IsChecked = this.IsEnToChineseDictEnabled;

                var isShowSpacesText = this.configManager.Get("IsShowSpaces");
                if (string.IsNullOrEmpty(isShowSpacesText) == false)
                {
                    bool ss;
                    if (bool.TryParse(isShowSpacesText, out ss))
                    {
                        this.IsShowSpaces = ss;
                    }
                }
                else
                {
                    this.isShowSpaces = false;//默认关闭
                }
                miShowSpaces.IsChecked = this.IsShowSpaces;

                var isShowEndOfLineText = this.configManager.Get("IsShowEndOfLine");
                if (string.IsNullOrEmpty(isShowEndOfLineText) == false)
                {
                    bool sel;
                    if (bool.TryParse(isShowEndOfLineText, out sel))
                    {
                        this.IsShowEndOfLine = sel;
                    }
                }
                else
                {
                    this.isShowEndOfLine = false;//默认关闭
                }
                miShowEndOfLine.IsChecked = this.IsShowEndOfLine;

                var isShowTabsText = this.configManager.Get("IsShowTabs");
                if (string.IsNullOrEmpty(isShowTabsText) == false)
                {
                    bool st;
                    if (bool.TryParse(isShowTabsText, out st))
                    {
                        this.IsShowTabs = st;
                    }
                }
                else
                {
                    this.isShowTabs = false;//默认关闭
                }
                miShowTabs.IsChecked = this.IsShowTabs;

                var showTitleInWorkspaceManagerText = this.configManager.Get("ShowTitleInWorkspaceManager");
                if (string.IsNullOrWhiteSpace(showTitleInWorkspaceManagerText) == false)
                {
                    bool stiwm;
                    if (bool.TryParse(showTitleInWorkspaceManagerText, out stiwm))
                    {
                        this.ShowTitleInWorkspaceManager = stiwm;
                    }
                }
                else
                {
                    this.showTitleInWorkspaceManager = false;//默认关闭
                }
                ckxShowTitle.IsChecked =
                miShowTitleInWorkspaceManager.IsChecked = this.ShowTitleInWorkspaceManager;

                var rememberOpenedFilesConfig = this.configManager.Get("RememberOpenedFiles");
                if (string.IsNullOrEmpty(rememberOpenedFilesConfig) == false)
                {
                    bool r;
                    if (bool.TryParse(rememberOpenedFilesConfig, out r))
                    {
                        this.RememberOpenedFiles = r;
                    }
                }
                else
                {
                    this.RememberOpenedFiles = true;//默认打开
                }
                miRememberOpenedFiles.IsChecked = this.RememberOpenedFiles;
                LoadRememberOpenedFiles();//会自动判断要不要载入。

                var selectCellFirst = this.configManager.Get("SelectCellFirst");
                if (string.IsNullOrEmpty(selectCellFirst) == false)
                {
                    bool se;
                    if (bool.TryParse(selectCellFirst, out se))
                    {
                        this.SelectCellFirst = se;
                    }
                }
                miSelectCellFirst.IsChecked = this.SelectCellFirst;

                var htmlHeadersCollapse = this.configManager.Get("HtmlHeadersCollapse");
                if (string.IsNullOrEmpty(htmlHeadersCollapse) == false)
                {
                    HtmlHeadersCollapseType hhct;
                    var result = Enum.TryParse<HtmlHeadersCollapseType>(htmlHeadersCollapse, out hhct);
                    if (result)
                    {
                        this.HtmlHeadersCollapse = hhct;
                    }
                }

                switch (this.HtmlHeadersCollapse)
                {
                    case HtmlHeadersCollapseType.Auto:
                        {
                            miAutoCollapseHtmlHeaders.IsChecked = true;
                            miManualCollapseHtmlHeaders.IsChecked = false;
                            miNoCollapseHtmlHeaders.IsChecked = false;
                            break;
                        }
                    case HtmlHeadersCollapseType.Manual:
                        {
                            miAutoCollapseHtmlHeaders.IsChecked = false;
                            miManualCollapseHtmlHeaders.IsChecked = true;
                            miNoCollapseHtmlHeaders.IsChecked = false;
                            break;
                        }
                    //case HtmlHeadersCollapseType.NO:
                    default:
                        {
                            miAutoCollapseHtmlHeaders.IsChecked = false;
                            miManualCollapseHtmlHeaders.IsChecked = false;
                            miNoCollapseHtmlHeaders.IsChecked = true;
                            break;
                        }
                }

                var autoNumberHeaders = this.configManager.Get("AutoNumberHeaders");
                if (string.IsNullOrEmpty(autoNumberHeaders) == false)
                {
                    bool an;
                    if (bool.TryParse(autoNumberHeaders, out an))
                    {
                        this.AutoNumberHeaders = an;
                    }
                }
                miAutoNumberHeaders.IsChecked = this.AutoNumberHeaders;

                //miFillblankMode.IsChecked = !miFillblankMode.IsChecked;
                //this.ConfigManager.Set("CompileCodeToFillBlank", miFillblankMode.IsChecked.ToString());
                var compileCodeToFillBlank = this.configManager.Get("CompileCodeToFillBlank");
                if (string.IsNullOrEmpty(compileCodeToFillBlank) == false)
                {
                    bool cctfb;
                    if (bool.TryParse(compileCodeToFillBlank, out cctfb))
                    {
                        this.CompileCodeToFillBlank = cctfb;
                    }
                }
                miFillblankMode.IsChecked = this.CompileCodeToFillBlank;

                var hideChoiceQuestionAnswer = this.configManager.Get("HideExamAnswer");
                if (string.IsNullOrEmpty(hideChoiceQuestionAnswer) == false)
                {
                    bool hcqa;
                    if (bool.TryParse(hideChoiceQuestionAnswer, out hcqa))
                    {
                        this.HideExamAnswer = hcqa;
                    }
                }
                miHideExamAnswer.IsChecked = this.HideExamAnswer;

                //TextAutoWrap，文本自动折行
                var textAutoWrap = this.configManager.Get("TextAutoWrap");
                if (string.IsNullOrEmpty(textAutoWrap) == false)
                {
                    bool txtAutoWrap;
                    if (bool.TryParse(textAutoWrap, out txtAutoWrap))
                    {
                        this.textAutoWrap = txtAutoWrap;//注意，不能使用属性，会引起无限递归
                    }
                }
                miTextAutoWrap.IsChecked = this.TextAutoWrap;

                var isCloseToIconText = this.ConfigManager.Get("CloseToIcon");
                if (string.IsNullOrWhiteSpace(isCloseToIconText) == false)
                {
                    this.isCloseToIcon = bool.Parse(isCloseToIconText);
                }
                miCloseToIcon.IsChecked = this.isCloseToIcon;

                var encoding = this.ConfigManager.Get("Encoding");
                if (string.IsNullOrEmpty(encoding) == false)
                {
                    RefreshEncoding(encoding);
                }
                else
                {
                    RefreshEncoding("utf-8");
                }

                var searchArea = this.ConfigManager.Get("SearchRange");
                if (string.IsNullOrWhiteSpace(searchArea) == false)
                {
                    foreach (var item in cmbSearchArea.Items)
                    {
                        var ci = item as ComboBoxItem;
                        if (ci == null) continue;

                        if ((ci.Tag as string) == searchArea)
                        {
                            cmbSearchArea.SelectedItem = ci;
                        }
                    }
                }

                var windowStateText = this.ConfigManager.Get("WindowState");
                if (string.IsNullOrWhiteSpace(windowStateText) == false)
                {
                    switch (windowStateText)
                    {
                        case "Maximized":
                            {
                                this.WindowState = WindowState.Maximized;
                                break;
                            }
                        default:
                            {
                                this.WindowState = WindowState.Normal;
                                break;
                            }
                    }
                }

                var fontSizeText = this.ConfigManager.Get("FontSize");
                if (string.IsNullOrWhiteSpace(fontSizeText) == false)
                {
                    double newFontSize;
                    if (double.TryParse(fontSizeText, out newFontSize))
                    {
                        this.mainTabControl.FontSize = newFontSize;
                        RefreshFontSize();
                    }
                }

                var ignoreEncryptedFiles = this.ConfigManager.Get("IgnoreEncryptedFiles");
                if (string.IsNullOrWhiteSpace(ignoreEncryptedFiles) == false)
                {
                    bool ignoreEncryptedFilesValue;
                    if (bool.TryParse(ignoreEncryptedFiles, out ignoreEncryptedFilesValue))
                    {
                        this.IgnoreEncryptedFiles = ignoreEncryptedFilesValue;
                    }
                }
                miIgnoreEncryptedFile.IsChecked = this.IgnoreEncryptedFiles;

                // 刷新查找、替换按钮的可用状态
                RefreshFindButtonsStatus();

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
            }
        }

        /// <summary>
        /// 编译后的 Html 使用的主题配色的标记文本。
        /// </summary>
        public string ThemeText
        {
            get
            {
                if (cmbColor.SelectedItem == null)
                {
                    cmbColor.SelectedIndex = 0;
                    return "light";//默认
                }

                return (cmbColor.SelectedItem as ComboBoxItem).Tag.ToString();
            }
        }

        /// <summary>
        /// 用于刷新树型框列表。
        /// </summary>
        private WorkspaceManager workspaceManager;
        /// <summary>
        /// 工作区管理器。
        /// </summary>
        internal WorkspaceManager WorkspaceManager
        {
            get
            {
                if (this.workspaceManager == null)
                    this.workspaceManager = new WorkspaceManager(this);
                return workspaceManager;
            }
        }

        private XmlConfigManager configManager;
        /// <summary>
        /// 用于保存配置的Xml配置管理器。
        /// </summary>
        public XmlConfigManager ConfigManager
        {
            get { return configManager; }
        }

        /// <summary>
        /// 让当前活动编辑器执行“撤销”操作。
        /// </summary>
        public void Undo()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            if (eti.EditorBase.CanUndo)
            {
                eti.EditorBase.Undo();
            }
        }

        /// <summary>
        /// 让当前活动编辑器执行“重做”操作。
        /// </summary>
        public void Redo()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            if (eti.EditorBase.CanRedo)
            {
                eti.EditorBase.Redo();
            }
        }

        /// <summary>
        /// 保存当前活动编辑器中的文档。
        /// </summary>
        /// <param name="formatBeforeSave">保存前是否格式化 Markdown 文本。</param>
        private void SaveDocment(bool formatBeforeSave)
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            if (eti.IsModified == false) return;

            string oldFileFullPath = eti.FullFilePath;

            if (formatBeforeSave)
            {
                //eti.EditorBase.Text = eti.GetFormatedMarkdownText();
                eti.EditorBase.Format();
            }

            if (File.Exists(eti.FullFilePath))
            {
                eti.SaveDocument();
            }
            else
            {
                SaveNewFile(ActivedEditor);
            }

            if (eti.FullFilePath != oldFileFullPath)
            {
                this.workspaceManager.Refresh(eti.FullFilePath);//刷新工作区文件列表。
            }
        }

        /// <summary>
        /// 仅用以保存不是从“工作区”创建的、且不是从磁盘打开的文件。
        /// </summary>
        private string SaveNewFile(MarkdownEditor editor)
        {
            if (editor == null) return "未指定目标编辑器。";

            WorkspaceTreeViewItem wtvi;
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("　　请先在主界面左侧工作区浏览窗口中选定一个目录来创建文件。" +
                    "如果看不到工作区浏览窗口，请按F12键显示左工具栏。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
                return "未选中保存目录。";
            }
            else
            {
                wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            }

            string directoryPath;
            WorkspaceTreeViewItem destWtvi;

            if (wtvi.IsMarkdownFilePath)
            {
                directoryPath = wtvi.ParentDirectory;
                destWtvi = wtvi.ParentWorkspaceTreeViewItem;
            }
            else
            {
                directoryPath = wtvi.FullPath;
                destWtvi = wtvi;
            }

            if (Directory.Exists(directoryPath) == false)
            {
                var lastIndex = directoryPath.LastIndexOf("\\");
                if (lastIndex < 0)
                {
                    LMessageBox.Show("　　只能在目录下新建文件。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return "指定位置不是目录。";
                }

                var parentDirectory = directoryPath.Substring(0, lastIndex);
                if (Directory.Exists(parentDirectory) == false)
                {
                    LMessageBox.Show("　　只能在目录下新建文件。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return "指定位置不是目录。";
                }

                directoryPath = parentDirectory;
            }

            if (directoryPath.EndsWith("~") || directoryPath.EndsWith("~\\"))
            {
                LMessageBox.Show("　　以波形符结尾的目录是程序自动添加的资源目录，不能在其中再建立MarkDown文件。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return "指定位置不是合法目录。";
            }

            if (directoryPath.EndsWith("\\") == false) directoryPath += "\\";

            string newShortFileName = InputBox.Show(Globals.AppName, "请输入新文件名：", "", true,
                   "说明：\r\n　　⑴不需要输入后缀名；\r\n　　⑵请尽可能设定有意义的文件名。\r\n　　⑶不能以下划线开头。\r\n　　因为此文件名很可能将来被其它文件引用。所以，如非必要，创建之后请勿随意更改文件名。");

            if (newShortFileName == null) return "用户放弃保存文件。";//用户放弃新建文件。

            if (newShortFileName.Contains(".") == false) newShortFileName += ".md";

            if (string.IsNullOrEmpty(newShortFileName))
            {
                LMessageBox.Show("　　文件名称不能为空！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                return "用户未提供文件名。";
            }

            try
            {
                var newFilePath = directoryPath + newShortFileName;
                var docTitle = newShortFileName.EndsWith(".md") ? newShortFileName.Substring(0, newShortFileName.Length - 3) : newShortFileName;
                editor.EditorBase.Save(newFilePath);
                editor.FullFilePath = newFilePath;

                WorkspaceTreeViewItem newtvi = new WorkspaceTreeViewItem(newFilePath, Globals.MainWindow);
                destWtvi.Items.Insert(0, newtvi);
                newtvi.IsSelected = true;

                return string.Empty;
            }
            catch (Exception ex)
            {
                LMessageBox.Show("　　新建文件失败！错误消息：\r\n" + ex.Message + "\r\n" + ex.StackTrace,
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                return ex.Message;
            }
        }

        /// <summary>
        /// 保存当前打开的所有文档。
        /// </summary>
        private void SaveAllDocuments()
        {
            List<MarkdownEditor> needSavingDocumentList = new List<MarkdownEditor>();
            foreach (var item in this.mainTabControl.Items)
            {
                MarkdownEditor eti = item as MarkdownEditor;
                if (eti != null && eti.IsModified)
                {
                    needSavingDocumentList.Add(eti);
                }
            }

            if (needSavingDocumentList.Count > 0)
            {
                string saveInfo;
                foreach (MarkdownEditor eti in needSavingDocumentList)
                {
                    if (miFormatBeforeSave.IsChecked)
                    {
                        eti.EditorBase.Text = eti.FormatedMarkdownText();
                    }
                    saveInfo = eti.SaveDocument();
                    if (saveInfo == "用户取消操作")
                    {
                        LMessageBox.Show("　　取消了操作。未能保存所有需要保存的文档。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        return;
                    }
                    else if (saveInfo != string.Empty)
                    {
                        LMessageBox.Show("　　未能保存所有需要保存的文件。错误消息如下：\r\n" + saveInfo, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        return;//不关闭
                    }
                }
            }

            string shouldSelItemPath = null;
            WorkspaceTreeViewItem wi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (wi != null)
            {
                shouldSelItemPath = wi.FullPath;
            }
            this.workspaceManager.Refresh(shouldSelItemPath);//刷新工作区文件列表。
        }

        /// <summary>
        /// 关闭程序前做些清理、记录工作。
        /// </summary>
        void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (isForceExit == false)
            {
                if (this.IsCloseToIcon)
                {
                    this.Hide();
                    e.Cancel = true;
                    return;
                }
            }

            List<MarkdownEditor> needSavingDocumentList = new List<MarkdownEditor>();
            foreach (var item in this.mainTabControl.Items)
            {
                MarkdownEditor eti = item as MarkdownEditor;
                if (eti != null && eti.IsModified)
                {
                    needSavingDocumentList.Add(eti);
                }
            }

            if (needSavingDocumentList.Count > 0)
            {
                MessageBoxResult r = LMessageBox.Show(string.Format("　　有 {0} 个文档已被修改，要保存吗？", needSavingDocumentList.Count),
                    Globals.AppName, MessageBoxButton.YesNoCancel, MessageBoxImage.Warning);
                switch (r)
                {
                    case MessageBoxResult.Yes:
                        {
                            string saveInfo;
                            foreach (MarkdownEditor eti in needSavingDocumentList)
                            {
                                saveInfo = eti.SaveDocument();
                                if (saveInfo == "用户取消操作")
                                {
                                    e.Cancel = true;
                                    isForceExit = false;//还原
                                    return;//不关闭
                                }
                                else if (saveInfo != string.Empty)
                                {
                                    LMessageBox.Show(saveInfo, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                                    e.Cancel = true;
                                    isForceExit = false;//还原
                                    return;//不关闭
                                }
                            }

                            break;
                        }
                    case MessageBoxResult.Cancel:
                        {
                            e.Cancel = true;
                            isForceExit = false;//还原
                            return;
                        }
                        //default://No,直接关闭窗口
                }
            }

            RememberOpenedFilesForWorkspace();

            this.ConfigManager.Set("SearchRange", (cmbSearchArea.SelectedItem as ComboBoxItem).Tag.ToString());
            this.ConfigManager.Set("WindowState", this.WindowState.ToString());
            //退出托盘图标
            notifyIcon?.Dispose();
        }

        /// <summary>
        /// 记录所有当前打开的文档的路径，以便下次启动程序时自动打开。
        /// </summary>
        private void RememberOpenedFilesForWorkspace()
        {
            if (this.RememberOpenedFiles)
            {
                //重置工作区配置文件管理器
                this.workspaceConfigManager = null;

                StringBuilder sb = new StringBuilder();
                foreach (var item in this.mainTabControl.Items)
                {
                    MarkdownEditor me = item as MarkdownEditor;
                    if (me == null || File.Exists(me.FullFilePath) == false) continue;

                    sb.Append(me.FullFilePath);
                    sb.Append(";");
                }

                this.WorkspaceConfigManager.Set("OpenedFiles", sb.ToString());

                if (this.mainTabControl.SelectedItem == null)
                {
                    this.WorkspaceConfigManager.Set("ActiveDocumentFullPath", "");
                }
                else
                {
                    var editor = this.mainTabControl.SelectedItem as MarkdownEditor;
                    if (editor == null)
                    {
                        this.WorkspaceConfigManager.Set("ActiveDocumentFullPath", "");
                    }
                    else
                    {
                        this.WorkspaceConfigManager.Set("ActiveDocumentFullPath", editor.FullFilePath);
                    }
                }
            }
        }

        /// <summary>
        /// 处理不会在工作区管理器和编辑器操作之间产生冲突的快捷键。
        /// 处理可能存在冲突，但是工作区管理器不需要使用或需要屏蔽其功能的快捷键。
        /// </summary>
        void MainWindow_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            KeyStates ksRShift = Keyboard.GetKeyStates(Key.RightShift);
            KeyStates ksLShift = Keyboard.GetKeyStates(Key.LeftShift);
            KeyStates ksLAlt = Keyboard.GetKeyStates(Key.LeftAlt);
            KeyStates ksRAlt = Keyboard.GetKeyStates(Key.RightAlt);
            KeyStates ksLCtrl = Keyboard.GetKeyStates(Key.LeftCtrl);
            KeyStates ksRCtrl = Keyboard.GetKeyStates(Key.RightCtrl);

            bool isCtrl, isShift, isAlt;

            isShift = (ksLShift & KeyStates.Down) > 0 || (ksRShift & KeyStates.Down) > 0;
            isCtrl = (ksLCtrl & KeyStates.Down) > 0 || (ksRCtrl & KeyStates.Down) > 0;
            isAlt = (ksLAlt & KeyStates.Down) > 0 || (ksRAlt & KeyStates.Down) > 0;
            switch (e.Key)
            {
                case Key.N:
                    {
                        if (isCtrl && isShift == false && isAlt == false)
                        {
                            NewFile(false);
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.T:
                    {
                        if (isCtrl)
                        {
                            if (isShift)//Ctrl+Shift+T
                            {
                                FormatAsTextTable();
                                e.Handled = true;//否则会插入一个字母t
                            }
                            else
                            {
                                if (isAlt == false)//Ctrl+T
                                {
                                    NewFile(false);
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.S:
                    {
                        if (isCtrl && isAlt == false)
                        {
                            if (isShift)
                            {
                                SaveAllDocuments();
                                e.Handled = true;
                            }
                            else
                            {
                                SaveDocment(miFormatBeforeSave.IsChecked);
                                e.Handled = true;
                            }
                        }
                        break;
                    }
                case Key.O:
                    {
                        if (isCtrl && isShift == false && isAlt == false)
                        {
                            miOpenDocument_Click(sender, e);
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.OemPlus:
                    {
                        if (isCtrl && isAlt == false)
                        {
                            if (isShift)
                            {
                                FontSizeUp();
                                e.Handled = true;
                            }
                            else
                            {
                                var selEditor = this.mainTabControl.SelectedItem as MarkdownEditor;
                                if (selEditor != null)
                                {
                                    selEditor.EditorBase.FindNextHeader();
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.OemMinus:
                    {
                        if (isCtrl && isAlt == false)
                        {
                            if (isShift)
                            {
                                FontSizeDown();
                                e.Handled = true;
                            }
                            else
                            {
                                var selEditor = this.mainTabControl.SelectedItem as MarkdownEditor;
                                if (selEditor != null)
                                {
                                    selEditor.EditorBase.FindPreviewHeader();
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.D0:
                    {
                        if (isCtrl && isShift == false && isAlt == false)
                        {
                            this.mainTabControl.FontSize = 16;//默认字号16。
                            RefreshFontSize();
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.F1:
                    {
                        if (!isAlt)
                        {
                            if (isShift)
                            {
                                cdLeftToolsArea.Width = new GridLength(2, GridUnitType.Star);
                                cdMainEditArea.Width =
                                    cdRightToolsArea.Width = new GridLength(0);
                            }
                            else
                            {
                                if (isCtrl)//Ctrl+F1显示帮助
                                {
                                    ShowHelp();
                                }
                                else
                                {
                                    SwitchLeftToolBarToggle();
                                }
                            }
                        }
                        break;
                    }
                case Key.U:
                    {
                        if (isCtrl && !isAlt)
                        {
                            if (isShift)
                            {
                                WrapWithSTag();
                            }
                            else
                            {
                                WrapWithUTag();
                            }
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.F5:
                    {
                        if (!isCtrl && !isAlt)
                        {
                            if (isShift)
                            {
                                PreviewAsFullScreen();
                            }

                            CompileAndPreviewHtml();
                        }
                        break;
                    }
                case Key.F11:
                    {
                        if (!isCtrl && !isAlt && !isShift)
                        {
                            SwitchFullScreen(mainMenu.Visibility == Visibility.Visible);
                        }
                        break;
                    }
                case Key.F12:
                    {
                        if (!isCtrl && !isAlt)
                        {
                            if (isShift == false)
                            {
                                SwitchRightToolBarToggle();
                            }
                            else
                            {
                                PreviewAsFullScreen();
                            }
                        }
                        break;
                    }
                case Key.D:
                    {
                        if (isShift && isCtrl && !isAlt)
                        {
                            InsertDateText();
                        }
                        break;
                    }
                case Key.G:
                    {
                        if (isCtrl && !isShift && !isAlt)
                        {
                            //还是给个窗口让用户选择是跳转到：
                            //链接（网址 图像链接 Markdown链接）/标题/TODO标记/任务列表

                            ContextMenu jumpContextMenu = new ContextMenu()
                            {
                                FontFamily = this.FontFamily,
                                FontSize = 14,
                                SnapsToDevicePixels = true,
                            };
                            TextOptions.SetTextFormattingMode(jumpContextMenu, TextFormattingMode.Display);

                            MenuItem mciGotoLink = new MenuItem()
                            {
                                Header = "跳转到链接[含图像链接](_L)",
                                ToolTip = "包括图像链接",
                            };
                            mciGotoLink.Click += MciGotoLink_Click;
                            jumpContextMenu.Items.Add(mciGotoLink);

                            MenuItem mciGotoHeader = new MenuItem()
                            {
                                Header = "跳转到标题(_H)",
                            };
                            mciGotoHeader.Click += MciGotoHeader_Click;
                            jumpContextMenu.Items.Add(mciGotoHeader);

                            MenuItem mciGotoTask = new MenuItem()
                            {
                                Header = "跳转到任务项(_T)",
                            };
                            mciGotoTask.Click += MciGotoTask_Click;
                            jumpContextMenu.Items.Add(mciGotoTask);

                            MenuItem mciGotoTodoComment = new MenuItem()
                            {
                                Header = "跳转到 TODO 项(_D)",
                            };
                            mciGotoTodoComment.Click += MciGotoTodoComment_Click;
                            jumpContextMenu.Items.Add(mciGotoTodoComment);

                            var edit = ActivedEditor;
                            if (edit != null)
                            {
                                var line = edit.EditorBase.Document.GetLineByOffset(edit.EditorBase.SelectionStart);
                                var lineText = edit.EditorBase.Document.GetText(line.Offset, line.Length);

                                if (CustomMarkdownSupport.IsTodoCommentLine(lineText))
                                {
                                    mciGotoTodoComment.Focus();
                                }
                                else if (CustomMarkdownSupport.IsDateLine(lineText) ||
                                   CustomMarkdownSupport.IsTaskLine(lineText))
                                {
                                    mciGotoTask.Focus();
                                }
                                else
                                {

                                }
                            }

                            jumpContextMenu.Placement = PlacementMode.Center;
                            jumpContextMenu.PlacementTarget = this;
                            jumpContextMenu.IsOpen = true;

                            e.Handled = true;
                        }
                        break;
                    }
                case Key.E:
                    {
                        if (isCtrl && !isAlt)
                        {
                            if (isShift)
                            {
                                miIsAutoCompletionEnabled_Clicked(sender, e);
                            }
                            else
                            {
                                miIsExamEnabled_Click(sender, e);
                            }
                        }
                        break;
                    }
                case Key.R:
                    {
                        if (isCtrl && !isAlt)
                        {
                            if (isShift)
                            {
                                miWrapWithRegionMark_Click(sender, e);
                            }
                            else
                            {
                                miTextWrap_Click(sender, e);
                            }
                        }
                        break;
                    }
                case Key.K:
                    {
                        if (isCtrl && !isAlt && !isShift)
                        {
                            miInsertLinkMark_Click(sender, e);
                        }
                        break;
                    }
                case Key.F:
                    {
                        if (isCtrl)
                        {
                            if (isShift)
                            {
                                if (isAlt == false)
                                {
                                    //Ctrl+Shift+F
                                    Format();
                                }
                                e.Handled = true;
                            }
                        }
                        break;
                    }
                case Key.H:
                    {
                        // Ctrl+H
                        if (isCtrl && !isAlt && !isShift)
                        {
                            if (rdFindAndReplace.ActualHeight <= 40)
                            {
                                rdFindAndReplace.Height = new GridLength(140, GridUnitType.Auto);
                                cmbFindText.UpdateLayout();
                            }

                            var editor = ActivedEditor;
                            if (editor != null)
                            {
                                cmbFindText.Text = editor.EditorBase.SelectedText;
                            }
                            else return;

                            cmbSearchArea.SelectedIndex = 0;

                            if (cmbFindText.Text.Length > 0)
                            {
                                cmbReplaceTextInputBox.Focus();
                            }
                            else
                            {
                                cmbFindText.Focus();
                            }
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.L:
                    {
                        if (isCtrl && isShift == false && isAlt == false)
                        {
                            SelecetLine();
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.F3:
                    {
                        if (isCtrl == false && isAlt == false)
                        {
                            if (isShift)
                            {
                                if (cmbFindText.Text.Length > 0)
                                {
                                    cbSearchUp.IsChecked = true;
                                    FindNext(cmbFindText.Text);
                                }
                            }
                            else
                            {
                                if (cmbFindText.Text.Length > 0)
                                {
                                    cbSearchUp.IsChecked = false;
                                    FindNext(cmbFindText.Text);
                                }
                            }
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.Escape:
                    {
                        if (isCtrl == false && isShift == false && isAlt == false)
                        {
                            var efi = this.mainTabControl.SelectedItem as MarkdownEditor;
                            if (efi != null) // && efi.EditorBase.IsSearchPanelOpened)
                            {
                                if (efi.EditorBase.CompletionWindow != null &&
                                    efi.EditorBase.CompletionWindow.Visibility == Visibility.Visible)
                                {
                                    efi.EditorBase.CompletionWindow.Close();
                                }

                                //CloseSearchPanel();//AvalonEdit的SearchPanel总是有些问题，放弃了
                                if (rdFindAndReplace.ActualHeight > 0)
                                {
                                    rdFindAndReplace.Height = new GridLength(0);
                                    FocusActiveEditor();
                                    e.Handled = true;
                                    break;
                                }
                            }

                            if (this.WindowStyle == WindowStyle.None)
                            {
                                //全屏状态
                                SwitchFullScreen(false);
                            }
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.OemTilde://波形符键（反引号键）
                    {
                        if (isCtrl)
                        {
                            var mei = this.mainTabControl.SelectedItem as MarkdownEditor;
                            if (mei != null)
                            {
                                mei.EditorBase.WrapTextWithAntiQuotes();
                            }
                        }
                        break;
                    }
                case Key.OemQuotes:
                    {
                        if (isCtrl && isAlt == false)
                        {
                            var mei = this.mainTabControl.SelectedItem as MarkdownEditor;
                            if (mei != null)
                            {
                                mei.EditorBase.WrapTextWithQuotes(isShift);
                            }
                        }
                        break;
                    }
                case Key.OemPipe:
                    {
                        if (isCtrl && !isShift && !isAlt)
                        {
                            //插入新列
                            var selEditor = this.mainTabControl.SelectedItem as MarkdownEditor;
                            if (selEditor != null)
                            {
                                selEditor.EditorBase.InsertTableColumn();
                                e.Handled = true;
                            }
                        }
                        break;
                    }
                case Key.Q:
                    {
                        //定位到资源搜索框，这样就可以直接输入文本了。
                        if (isCtrl && !isShift && !isAlt)
                        {
                            cmbSearchResource.Focus();
                        }
                        break;
                    }
                case Key.F6:
                    {
                        if (!isCtrl && !isAlt && !isShift)
                        {
                            //演示试题
                            if (IsExamEnabled) Globals.MainWindow.PresentationExams();
                        }
                        break;
                    }
                case Key.D1:
                    {
                        if (isCtrl)
                        {
                            if (isAlt && !isShift)
                            {
                                ChangeWorkspaceByShortCut(isCtrl, isShift, isAlt, 1);
                                e.Handled = true;
                            }
                            else
                            {
                                if (!isShift)
                                {
                                    //Ctrl+数字
                                    SwitchTitleLevel(1);
                                }
                            }
                        }
                        break;
                    }
                case Key.D2:
                    {
                        if (isCtrl)
                        {
                            if (isAlt && !isShift)
                            {
                                ChangeWorkspaceByShortCut(isCtrl, isShift, isAlt, 2);
                                e.Handled = true;
                            }
                            else
                            {
                                if (!isShift)
                                {
                                    //Ctrl+数字
                                    SwitchTitleLevel(2);
                                }
                            }
                        }
                        break;
                    }
                case Key.D3:
                    {
                        if (isCtrl)
                        {
                            if (isAlt && !isShift)
                            {
                                ChangeWorkspaceByShortCut(isCtrl, isShift, isAlt, 3);
                                e.Handled = true;
                            }
                            else
                            {
                                if (!isShift)
                                {
                                    //Ctrl+数字
                                    SwitchTitleLevel(3);
                                }
                            }
                        }
                        break;
                    }
                case Key.D4:
                    {
                        if (isCtrl)
                        {
                            if (isAlt && !isShift)
                            {
                                ChangeWorkspaceByShortCut(isCtrl, isShift, isAlt, 4);
                                e.Handled = true;
                            }
                            else
                            {
                                if (!isShift)
                                {
                                    //Ctrl+数字
                                    SwitchTitleLevel(4);
                                }
                            }
                        }
                        break;
                    }
                case Key.D5:
                    {
                        if (isCtrl)
                        {
                            if (isAlt && !isShift)
                            {
                                ChangeWorkspaceByShortCut(isCtrl, isShift, isAlt, 5);
                                e.Handled = true;
                            }
                            else
                            {
                                if (!isShift)
                                {
                                    //Ctrl+数字
                                    SwitchTitleLevel(5);
                                }
                            }
                        }
                        break;
                    }
                case Key.D6:
                    {
                        if (isCtrl)
                        {
                            if (isAlt && !isShift)
                            {
                                ChangeWorkspaceByShortCut(isCtrl, isShift, isAlt, 6);
                                e.Handled = true;
                            }
                            else
                            {
                                if (!isShift)
                                {
                                    //Ctrl+数字
                                    SwitchTitleLevel(6);
                                }
                            }
                        }
                        break;
                    }
                case Key.D7:
                    {
                        if (isCtrl && isAlt && !isShift)
                        {
                            ChangeWorkspaceByShortCut(isCtrl, isShift, isAlt, 7);
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.D8:
                    {
                        if (isCtrl && isAlt && !isShift)
                        {
                            ChangeWorkspaceByShortCut(isCtrl, isShift, isAlt, 8);
                            e.Handled = true;
                        }
                        break;
                    }
                case Key.D9:
                    {
                        if (isCtrl && isAlt && !isShift)
                        {
                            ChangeWorkspaceByShortCut(isCtrl, isShift, isAlt, 9);
                            e.Handled = true;
                        }
                        break;
                    }
            }
        }

        /// <summary>
        /// 查找所有 TODO Comment，以便跳转到某个 TODO Comment。
        /// </summary>
        private void MciGotoTodoComment_Click(object sender, RoutedEventArgs e)
        {
            GotoTodoComment();
        }

        /// <summary>
        /// 查找所有任务列表，以便快速向某个任务列表跳转。
        /// </summary>
        private void MciGotoTask_Click(object sender, RoutedEventArgs e)
        {
            GotoTaskListItem();
        }

        /// <summary>
        /// 查找所有标题，以便快速向某个标题跳转。
        /// </summary>
        private void MciGotoHeader_Click(object sender, RoutedEventArgs e)
        {
            GotoHeader();
        }

        /// <summary>
        /// 跳转到当前活动编辑器中当前插入点位置处的链接（图像链接/文档链接/网址）指向的对象。
        /// </summary>
        private void MciGotoLink_Click(object sender, RoutedEventArgs e)
        {
            GotoLink();
        }

        /// <summary>
        /// 查找所有标题，以便快速向某个标题跳转。
        /// </summary>
        private void GotoHeader()
        {
            var gotoWindow = new GotoWindow()
            {
                WindowStartupLocation = WindowStartupLocation.CenterOwner,
                Owner = this,
                Title = Globals.AppName + " - 在标题间跳转",
            };

            FindHeaders(@"^ {0,3}#{1,6}.*", gotoWindow.treeView);
            gotoWindow.ShowDialog();
        }

        /// <summary>
        /// 查找所有任务列表，以便快速向某个任务列表跳转。
        /// </summary>
        private void GotoTaskListItem()
        {
            var gotoWindow = new GotoWindow()
            {
                WindowStartupLocation = WindowStartupLocation.CenterOwner,
                Owner = this,
                Title = Globals.AppName + " - 在任务列表间跳转",
            };

            FindTaskList("", gotoWindow.treeView);
            gotoWindow.ShowDialog();
        }

        /// <summary>
        /// 查找所有 TODO Comment，以便跳转到某个 TODO Comment。
        /// </summary>
        private void GotoTodoComment()
        {
            var gotoWindow = new GotoWindow()
            {
                WindowStartupLocation = WindowStartupLocation.CenterOwner,
                Owner = this,
                Title = Globals.AppName + " - 在 TODO 标记间跳转",
            };

            FindTodoComment((cmbSearchArea.SelectedItem as ComboBoxItem).Tag.ToString(), gotoWindow.treeView);
            gotoWindow.ShowDialog();
        }

        /// <summary>
        /// 跳转到当前活动编辑器中当前插入点位置处的链接（图像链接/文档链接/网址）指向的对象。
        /// </summary>
        private void GotoLink()
        {
            var edit = this.ActivedEditor;
            if (edit == null) return;

            if (edit.GotoAnchorInSameDocument()) return;

            var line = edit.EditorBase.Document.GetLineByOffset(edit.EditorBase.SelectionStart);
            var lineText = edit.EditorBase.Document.GetText(line.Offset, line.EndOffset - line.Offset);
            var startIndex = edit.EditorBase.SelectionStart - line.Offset;

            var leftIndex = lineText.IndexOf("](") + 1;
            var rightIndex = lineText.IndexOf(')', startIndex);
            if (leftIndex <= 0 || rightIndex <= 1 || rightIndex <= leftIndex + 1) return;

            var subText = lineText.Substring(leftIndex + 1, rightIndex - leftIndex - 1);
            if (subText.ToLower().Contains(".html"))
            {
                edit.OpenLinkedMarkdownFile();
            }
            else
            {
                try
                {
                    Regex urlReg = new Regex(@"([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?");
                    var matchUrl = urlReg.Match(subText);

                    if (matchUrl.Success)
                    {
                        var tmp = subText.ToLower();
                        if (tmp.StartsWith("http://") == false &&
                            tmp.StartsWith("https://") == false &&
                            tmp.StartsWith("ftp://") == false)
                        {
                            subText = "http://" + subText;
                        }

                        previewFrame.Source = new Uri(subText);
                        rightToolBar.SelectedIndex = 0;//显示预览页
                        return;
                    }
                    else edit.PreviewLinkedImageFile();
                }
                catch (Exception ex)
                {
                    var lower = subText.ToLower();

                    if (lower.EndsWith(".jpg") || lower.EndsWith(".jpeg") || lower.EndsWith(".gif") ||
                        lower.EndsWith(".png") || lower.EndsWith("bmp"))
                    {
                        edit.PreviewLinkedImageFile();
                    }
                    else
                    {
                        LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
                    }
                }
            }
        }

        /// <summary>
        /// 切换当前活动编辑器中当前行的标题层级。
        /// </summary>
        /// <param name="newLevel">新标题层级</param>
        private void SwitchTitleLevel(int newLevel)
        {
            var activeEditor = this.ActivedEditor;
            if (activeEditor == null) return;

            activeEditor.EditorBase.SwitchTitleLevel(newLevel);
        }

        /// <summary>
        /// 用快捷键切换当前工作区。通常是使用 Ctrl+Shift+Alt+数字键。
        /// </summary>
        /// <param name="isCtrl">Ctrl键状态。</param>
        /// <param name="isShift">Shift键状态。</param>
        /// <param name="isAlt">Alt键状态。</param>
        /// <param name="num">历史工作区列表中工作区条目的序号（1-9).</param>
        private void ChangeWorkspaceByShortCut(bool isCtrl, bool isShift, bool isAlt, int num)
        {
            if (isAlt && isCtrl && !isShift)
            {
                var index = num - 1;
                if (index < 0 || index >= lbxHistoryWorkspaces.Items.Count)
                {
                    LMessageBox.Show("　　历史工作区中没有对应条目！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
                    return;
                }

                lbxHistoryWorkspaces.SelectedIndex = index;

                if (tabControlLeftTools.SelectedIndex != 1)
                {
                    tabControlLeftTools.SelectedIndex = 1;
                }

                if (cdLeftToolsArea.ActualWidth < 140)
                {
                    cdLeftToolsArea.Width = new GridLength(2, GridUnitType.Star);
                }

                var item = lbxHistoryWorkspaces.Items[num - 1] as RecentDirectoryListBoxItem;
                if (item != null && item.DirectoryPath != Globals.PathOfWorkspace)
                {
                    var result = LMessageBox.Show("真的要把工作区切换到以下目录吗？\r\n\r\n　　" + item.DirectoryPath, Globals.AppName,
                         MessageBoxButton.YesNo, MessageBoxImage.Question);
                    if (result == MessageBoxResult.Yes)
                    {
                        ChangeWorkspace(item.DirectoryPath);
                    }
                }
            }
        }

        /// <summary>
        /// 以全屏方式预览当前 Markdown 文档编译后生成的 Html 网页文件。
        /// </summary>
        private void PreviewAsFullScreen()
        {
            mainToolBar.Visibility =
                mainMenu.Visibility =
                mainTabControl.Visibility =
                bottomToolsDockpanel.Visibility = Visibility.Collapsed;

            rightToolBar.Visibility = Visibility.Visible;
            rightToolBar.SelectedIndex = 0;

            cdLeftToolsArea.MinWidth = 0;
            cdMainEditArea.Width =
            cdLeftToolsArea.Width = new GridLength(0);
            cdRightToolsArea.Width = new GridLength(3, GridUnitType.Star);

            this.WindowState = System.Windows.WindowState.Maximized;

            //dpTitle.Visibility = Visibility.Collapsed;
            dpFind.Visibility = Visibility.Collapsed;

            rdFindAndReplace.Height = new GridLength(0);

            this.ToolTip = "Esc或F11键退出全屏";
            miPreviewFullScreen.IsChecked = true;
            miFullScreen.IsChecked = false;
            miFullScreenC.IsChecked = true;
        }

        /// <summary>
        /// 切换全屏状态。
        /// </summary>
        /// <param name="fullScreen">指明是否要将主窗口设置为全屏。</param>
        private void SwitchFullScreen(bool fullScreen)
        {
            if (fullScreen)
            {
                //cmbColor.Visibility =
                mainToolBar.Visibility =
                mainMenu.Visibility =
                bottomToolsDockpanel.Visibility = Visibility.Collapsed;

                cdLeftToolsArea.MinWidth = 0;
                rdFindAndReplace.Height =
                cdLeftToolsArea.Width = new GridLength(0, GridUnitType.Star);
                cdRightToolsArea.Width = new GridLength(0, GridUnitType.Star);

                this.WindowState = System.Windows.WindowState.Maximized;

                dpFind.Visibility = Visibility.Collapsed;

                rdFindAndReplace.Height = new GridLength(0);

                this.ToolTip = "Esc或F11键退出全屏";
                miFullScreen.IsChecked = true;
                miFullScreenC.IsChecked = true;
            }
            else
            {
                //cmbColor.Visibility =
                mainToolBar.Visibility =
                mainMenu.Visibility =
                mainTabControl.Visibility =
                bottomToolsDockpanel.Visibility = System.Windows.Visibility.Visible;

                cdLeftToolsArea.MinWidth = 40;
                cdLeftToolsArea.Width = new GridLength(2, GridUnitType.Star);
                cdMainEditArea.Width = new GridLength(3, GridUnitType.Star);
                cdRightToolsArea.Width = new GridLength(3, GridUnitType.Star);

                rdFindAndReplace.Height = new GridLength(0);

                //通常不需要退出最大化状态。2016年2月28日
                dpFind.Visibility = Visibility.Visible;

                this.ToolTip = null;
                miPreviewFullScreen.IsChecked = false;
                miFullScreen.IsChecked = false;
                miFullScreenC.IsChecked = false;
            }
        }

        /// <summary>
        /// 处理与主界面其它区域可能冲突的快捷键。
        /// 这样在主界面其它区仍然可以使用这些快捷键。
        /// 例如，可以在“历史工作区目录”继续使用 Ctrl+Shift+8，作为快捷键。
        /// </summary>
        void mainTabControl_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            KeyStates ksLShift = Keyboard.GetKeyStates(Key.LeftShift);
            KeyStates ksRShift = Keyboard.GetKeyStates(Key.RightShift);
            KeyStates ksLAlt = Keyboard.GetKeyStates(Key.LeftAlt);
            KeyStates ksRAlt = Keyboard.GetKeyStates(Key.RightAlt);
            KeyStates ksLCtrl = Keyboard.GetKeyStates(Key.LeftCtrl);
            KeyStates ksRCtrl = Keyboard.GetKeyStates(Key.RightCtrl);

            bool isCtrl, isShift, isAlt;

            isShift = (ksLShift & KeyStates.Down) > 0 || (ksRShift & KeyStates.Down) > 0;
            isCtrl = (ksLCtrl & KeyStates.Down) > 0 || (ksRCtrl & KeyStates.Down) > 0;
            isAlt = (ksLAlt & KeyStates.Down) > 0 || (ksRAlt & KeyStates.Down) > 0;
            switch (e.Key)
            {
                case Key.D8:
                    {
                        if (isCtrl && !isAlt)
                        {
                            if (this.mainTabControl.SelectedItem != null)
                            {
                                var mdi = this.mainTabControl.SelectedItem as MarkdownEditor;
                                if (mdi != null)
                                {
                                    mdi.EditorBase.SwitchListMark(!isShift);
                                    e.Handled = true;
                                }
                            }
                        }

                        break;
                    }
            }
        }

        /// <summary>
        /// 双击状态栏上的当前工作区路径标签，在 Windows Explorer 中打开该目录。
        /// </summary>
        private void tbxPathOfWorkspace_MouseLeftButtonDown_1(object sender, MouseButtonEventArgs e)
        {
            if (e.ClickCount == 2)
            {
                if (Directory.Exists(Globals.PathOfWorkspace) == false)
                {
                    LMessageBox.Show(string.Format("　　指定的工作区目录[{0}]不存在。这可能是因为程序被安装到Window系统盘的某个目录中，又未获得管理员权限，导致无法创建工作区目录。",
                        Globals.PathOfWorkspace), Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                System.Diagnostics.Process.Start("explorer.exe", $"\"{Globals.PathOfWorkspace}\"");
            }
        }

        /// <summary>
        /// 在工作区选定位置创建新 Markdown 文件。
        /// </summary>
        private void btnNew_Click_1(object sender, RoutedEventArgs e)
        {
            NewFile(false);
        }

        /// <summary>
        /// 在工作区选定位置创建新 Markdown 文件。
        /// </summary>
        private void miNewdocument_Click_1(object sender, RoutedEventArgs e)
        {
            NewFile(false);
        }

        /// <summary>
        /// 创建工作区选定目录条目对应的元 Markdonw 文件。
        /// </summary>
        private void miNewFolderMetaDocument_Click(object sender, RoutedEventArgs e)
        {
            NewFile(false, true);
        }

        /// <summary>
        /// 保存当前正在编辑的 Markdown 文档（活动编辑器中的文档）。
        /// </summary>
        private void btnSave_Click_1(object sender, RoutedEventArgs e)
        {
            SaveDocment(miFormatBeforeSave.IsChecked);
        }

        /// <summary>
        /// 保存当前正在编辑的 Markdown 文档（活动编辑器中的文档）。
        /// </summary>
        private void miSave_Click_1(object sender, RoutedEventArgs e)
        {
            SaveDocment(miFormatBeforeSave.IsChecked);
        }

        /// <summary>
        /// 打开指定路径的 Markdown 文件（一个或多个）。
        /// </summary>
        /// <param name="fullPathOfFiles">要打开的 Markdown 的文件的路径列表。</param>
        public void OpenDocuments(string[] fullPathOfFiles)
        {
            List<string> files = new List<string>();
            foreach (string s in fullPathOfFiles)
            {
                files.Add(s);
            }
            OpenDocuments(files);
        }

        /// <summary>
        /// 根据指定的路径列表打开这些 Markdown 文件。
        /// </summary>
        /// <param name="fullPathOfFiles">包含 Markdown 文件磁盘路径的列表。</param>
        public void OpenDocuments(List<string> fullPathOfFiles)
        {
            StringBuilder errorMsg = new StringBuilder();

            foreach (string s in fullPathOfFiles)
            {
                MarkdownEditor openedFileItem = null;

                foreach (var item in this.mainTabControl.Items)
                {
                    MarkdownEditor eti = item as MarkdownEditor;
                    if (eti != null && eti.FullFilePath == s)
                    {
                        openedFileItem = eti;
                        break;
                    }
                }

                if (openedFileItem != null)
                {
                    this.mainTabControl.SelectedItem = openedFileItem;
                    continue;//已打开的文档，不再重复打开，只是使其成为活动文档。
                }

                MarkdownEditor newDocument = new MarkdownEditor("md_" + (mainTabControl.Items.Count + 1),
                    this.IsExamEnabled, this.IsAutoCompletionEnabled, this.IsEnToChineseDictEnabled,
                    this.IsShowSpaces, this.IsShowEndOfLine, this.IsShowTabs, false);

                string openResult = newDocument.OpenDocument(s);
                if (openResult == string.Empty)
                {
                    this.mainTabControl.Items.Insert(this.mainTabControl.SelectedIndex + 1, newDocument);
                    this.mainTabControl.SelectedIndex += 1;
                }
                else
                {
                    errorMsg.Append(openResult);
                }

                newDocument.Saved += NewDocument_Saved;
            }

            string em = errorMsg.ToString();
            if (em != string.Empty)
            {
                LMessageBox.Show("　　打开文件时出现异常。消息如下：\r\n" + em,
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 创建一个新文件后，在工作区管理器中添加对应的条目。
        /// </summary>
        private void NewDocument_Saved(object sender, DocumentSavedEventArgs e)
        {
            var item = FindWorkspaceTreeViewItem(e.FileFullName);
            if (item == null) return;

            item.RefreshFileState();
        }

        /// <summary>
        /// 保存当前打开的所有 Markdown 文档。
        /// </summary>
        private void miSaveAll_Click_1(object sender, RoutedEventArgs e)
        {
            SaveAllDocuments();
        }

        /// <summary>
        /// 退出应用程序。
        /// </summary>
        private void miExit_Click_1(object sender, RoutedEventArgs e)
        {
            this.Close();//关闭主窗口即退出。
        }

        /// <summary>
        /// 让当前活动编辑器执行“撤销”操作。
        /// </summary>
        private void miUndo_Click_1(object sender, RoutedEventArgs e)
        {
            Undo();
        }

        /// <summary>
        /// 让当前活动编辑器执行“重做”操作。
        /// </summary>
        private void miRedo_Click_1(object sender, RoutedEventArgs e)
        {
            Redo();
        }

        /// <summary>
        /// 从当前活动编辑器剪切选定的文本。
        /// </summary>
        private void miCut_Click_1(object sender, RoutedEventArgs e)
        {
            Cut();
        }

        /// <summary>
        /// 从当前活动编辑器复制选定的文本。
        /// </summary>
        private void miCopy_Click_1(object sender, RoutedEventArgs e)
        {
            Copy();
        }

        /// <summary>
        /// 向当前活动编辑器粘贴数据。
        /// </summary>
        private void miPaste_Click_1(object sender, RoutedEventArgs e)
        {
            Paste();
        }

        /// <summary>
        /// 向当前活动编辑器粘贴代码（会对粘贴的代码自动添加行号）。
        /// </summary>
        private void miPasteCode_Click(object sender, RoutedEventArgs e)
        {
            PasteCode();
        }

        /// <summary>
        /// 向当前活动编辑器粘贴代码（会对粘贴的代码自动添加行号）。
        /// </summary>
        public void PasteCode()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            eti.PasteCode();
        }

        /// <summary>
        /// 让当前活动编辑器执行“全选”操作。
        /// </summary>
        private void miSelectAll_Click_1(object sender, RoutedEventArgs e)
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            eti.EditorBase.SelectAll();
        }

        /// <summary>
        /// 让当前活动编辑器执行“撤销”操作。
        /// </summary>
        private void btnUndo_Click_1(object sender, RoutedEventArgs e)
        {
            Undo();
        }

        /// <summary>
        /// 让当前活动编辑器执行“重做”操作。
        /// </summary>
        private void btnRedo_Click_1(object sender, RoutedEventArgs e)
        {
            Redo();
        }

        /// <summary>
        /// 从当前活动编辑器中剪切选定的文本。
        /// </summary>
        public void Cut()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            eti.EditorBase.Cut();
        }

        /// <summary>
        /// 从当前活动编辑器中剪切选定的文本。
        /// </summary>
        private void btnCut_Click_1(object sender, RoutedEventArgs e)
        {
            Cut();
        }

        /// <summary>
        /// 从当前活动编辑器中复制选定的文本。
        /// </summary>
        public void Copy()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            eti.EditorBase.Copy();
        }

        /// <summary>
        /// 从当前活动编辑器中复制选定的文本。
        /// </summary>
        private void btnCopy_Click_1(object sender, RoutedEventArgs e)
        {
            Copy();
        }

        /// <summary>
        /// 向当前活动编辑器粘贴文本。
        /// </summary>
        public void Paste()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            eti.Paste();
        }

        /// <summary>
        /// 向当前活动编辑器执行粘贴操作。（可能是文本，也可能是图像链接。）
        /// </summary>
        private void btnPaste_Click_1(object sender, RoutedEventArgs e)
        {
            Paste();
        }

        /// <summary>
        /// 向当前活动编辑器粘贴代码。
        /// </summary>
        private void btnPasteCode_Click(object sender, RoutedEventArgs e)
        {
            PasteCode();
        }

        /// <summary>
        /// 刷新各编辑器的字号。
        /// </summary>
        private void RefreshFontSize()
        {
            foreach (var item in this.mainTabControl.Items)
            {
                MarkdownEditor eti = item as MarkdownEditor;
                if (eti == null) continue;

                eti.EditorBase.Options.WordWrapIndentation = this.mainTabControl.FontSize * 2;
            }

            foreach (var item in this.rightToolBar.Items)
            {
                MarkdownEditor eti = item as MarkdownEditor;
                if (eti == null) continue;

                eti.EditorBase.Options.WordWrapIndentation = this.mainTabControl.FontSize * 2;
            }
        }

        /// <summary>
        /// 放大编辑器的字号。
        /// </summary>
        internal void FontSizeUp()
        {
            if (this.mainTabControl.FontSize <= 98)
            {
                this.mainTabControl.FontSize += 2;//最大字号100。

                //右工具栏中的对照区不能直接对右工具栏变化字号
                foreach (var item in this.rightToolBar.Items)
                {
                    MarkdownEditor eti = item as MarkdownEditor;
                    if (eti == null) continue;

                    eti.EditorBase.FontSize = this.mainTabControl.FontSize;
                }

                this.ConfigManager.Set("FontSize", this.mainTabControl.FontSize.ToString());
                RefreshFontSize();
            }
        }

        private void btnFontSizeUp_Click_1(object sender, RoutedEventArgs e)
        {
            FontSizeUp();
        }

        /// <summary>
        /// 缩小编辑器的字号。
        /// </summary>
        internal void FontSizeDown()
        {
            if (this.mainTabControl.FontSize >= 8)
            {
                this.mainTabControl.FontSize -= 2;//最小字号6。

                //右工具栏中的对照区不能直接对右工具栏变化字号
                foreach (var item in this.rightToolBar.Items)
                {
                    MarkdownEditor eti = item as MarkdownEditor;
                    if (eti == null) continue;

                    eti.EditorBase.FontSize = this.mainTabControl.FontSize;
                }

                this.ConfigManager.Set("FontSize", this.mainTabControl.FontSize.ToString());
                RefreshFontSize();
            }
        }

        /// <summary>
        /// 缩小编辑器的字号。
        /// </summary>
        private void btnFontSizeDown_Click_1(object sender, RoutedEventArgs e)
        {
            FontSizeDown();
        }

        /// <summary>
        /// 放大编辑器的字号。
        /// </summary>
        private void miFontSizeUp_Click_1(object sender, RoutedEventArgs e)
        {
            FontSizeUp();
        }

        /// <summary>
        /// 缩小编辑器的字号。
        /// </summary>
        private void miFontSizeDown_Click_1(object sender, RoutedEventArgs e)
        {
            FontSizeDown();
        }

        // <summary>
        /// 重置当前所有编辑器的默认字号。
        /// </summary>
        private void miFontSizeReset_Click_1(object sender, RoutedEventArgs e)
        {
            this.mainTabControl.FontSize = 16;//默认字号。
            RefreshFontSize();
        }

        // <summary>
        /// 给当前活动编辑器中的选定文本两侧加上加粗标志文本。（此操作不能跨行。）
        /// </summary>
        private void WrapWithBold()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            if (eti.EditorBase.SelectedText.Contains("\r") || eti.EditorBase.SelectedText.Contains("\n"))
            {
                LMessageBox.Show("  这个操作不允许跨行！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            eti.EditorBase.SwitchBold();
        }

        // <summary>
        /// 给当前活动编辑器中的选定文本两侧加上倾斜标志文本。（此操作不能跨行。）
        /// </summary>
        private void WrapWithItalic()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            if (eti.EditorBase.SelectedText.Contains("\r") || eti.EditorBase.SelectedText.Contains("\n"))
            {
                LMessageBox.Show("  这个操作不允许跨行！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            eti.EditorBase.SwitchItalic();
        }

        // <summary>
        /// 给当前活动编辑器中的选定文本两侧加上下划线标志文本。（此操作不能跨行。）
        /// </summary>
        private void WrapWithUTag()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            if (eti.EditorBase.SelectedText.Contains("\r") || eti.EditorBase.SelectedText.Contains("\n"))
            {
                LMessageBox.Show("  这个操作不允许跨行！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (eti.EditorBase.SelectedText.Length <= 0)
            {
                eti.EditorBase.SelectedText = "<u>" + eti.EditorBase.SelectedText + "</u>";
                eti.EditorBase.Select(eti.EditorBase.SelectionStart + 3, 0);
            }
            else
            {
                eti.EditorBase.SelectedText = "<u>" + eti.EditorBase.SelectedText + "</u>";
            }
        }

        /// <summary>
        /// 给当前活动编辑器中的选定文本两侧加上删除线标志文本。（此操作不能跨行。）
        /// </summary>
        private void WrapWithSTag()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            if (eti.EditorBase.SelectedText.Contains("\r") || eti.EditorBase.SelectedText.Contains("\n"))
            {
                LMessageBox.Show("  这个操作不允许跨行！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }
            if (eti.EditorBase.SelectedText.Length <= 0)
            {
                eti.EditorBase.SelectedText = "[=" + eti.EditorBase.SelectedText + "=]";
                eti.EditorBase.Select(eti.EditorBase.SelectionStart + 2, 0);
            }
            else
            {
                eti.EditorBase.SelectedText = "[=" + eti.EditorBase.SelectedText + "=]";
            }
        }

        /// <summary>
        /// 选取当前活动编辑器插入点位置的整行文本。（不能跨行，如果当前选择区是跨行的，只会选中选择区起始位置所在的行。）
        /// </summary>
        private void SelecetLine()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            eti.EditorBase.SelectLine();
        }

        /// <summary>
        /// 选取当前活动编辑器插入点位置的整行文本。（不能跨行，如果当前选择区是跨行的，只会选中选择区起始位置所在的行。）
        /// </summary>
        private void miSelectLine_Click_1(object sender, RoutedEventArgs e)
        {
            SelecetLine();
        }

        /// <summary>
        /// 弹出“关于”框，显示本程序相关信息。
        /// </summary>
        private void miAbout_Click_1(object sender, RoutedEventArgs e)
        {
            AboutBox abox = new AboutBox(this);
            abox.ShowDialog();
        }

        /// <summary>
        /// 保存当前打开的所有 Markdown 文档。
        /// </summary>
        private void btnSaveAll_Click(object sender, RoutedEventArgs e)
        {
            SaveAllDocuments();
        }

        /// <summary>
        /// 编译、预览当前活动编辑器生成的 Html 页面。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void previewHtml_Click(object sender, RoutedEventArgs e)
        {
            CompileAndPreviewHtml(true);
        }

        /// <summary>
        /// 以全屏方式预览由当前活动编辑器编译生成的 Html 网页。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void previewFullScreenHtml_Click(object sender, RoutedEventArgs e)
        {
            PreviewAsFullScreen();
            CompileAndPreviewHtml(true);
        }

        /// <summary>
        /// 编译当前活动编辑器为 Html 网页文件，并在浏览器中预览。
        /// </summary>
        /// <param name="callSystemDefaultExplorer">是否调用 Windows 默认网页浏览器来预览。默认为 false，表示直接使用 LME 自带的浏览器预览。</param>
        public void CompileAndPreviewHtml(bool callSystemDefaultExplorer = false)
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            if (eti.IsModified)
            {
                var messageResult = LMessageBox.Show("　　文档需要先保存，要继续吗？",
                    Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (messageResult != MessageBoxResult.Yes) return;

                if (Globals.MainWindow.miFormatBeforeSave.IsChecked)
                {
                    eti.FormatedMarkdownText();
                }

                string result;

                if (File.Exists(eti.FullFilePath))
                {
                    result = eti.SaveDocument();
                }
                else
                {
                    result = SaveNewFile(ActivedEditor);
                }

                if (result != string.Empty) return;
            }

            if (callSystemDefaultExplorer == false)
            {
                eti.CompileAndPreviewHtml();

                rightToolBar.SelectedIndex = 0;
                if (rightToolBar.Visibility != System.Windows.Visibility.Visible ||
                                rightToolBar.ActualWidth <= 0)
                {
                    SwitchRightToolBarToggle();
                }
            }
            else
            {
                eti.CompileAndPreviewHtml();
            }
        }

        /// <summary>
        /// 在工作区管理器当前选定项所指向的目录下，创建新 Markdown 文档。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void miNewFile_Click(object sender, RoutedEventArgs e)
        {
            NewFile(false);
        }

        /// <summary>
        /// 在工作区管理器当前选定项所指向的目录下，创建新示例文档。
        /// </summary>
        private void miNewSampleFile_Click(object sender, RoutedEventArgs e)
        {
            NewFile(true);
        }

        /// <summary>
        /// 在工作区管理器当前选定项的位置新建一个 Markdown 文档。
        /// 如果当前项代表目录，则在该目录下面创建新 Markdown 文档；
        /// 如果当前项代表某个 Markdown 文件，则在该 Markdown 文件所在的文件夹下创建新 Markdown 文件。
        /// </summary>
        /// <param name="isSampleFile">是否示例文档。如为 true，则创建的文档带内容。</param>
        /// <param name="isMetaFile">是否目录元文件。</param>
        private void NewFile(bool isSampleFile, bool isMetaFile = false)
        {
            WorkspaceTreeViewItem wtvi;
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("　　请先在主界面左侧工作区浏览窗口中选定一个目录来创建文件。" +
                    "如果看不到工作区浏览窗口，请按F12键显示左工具栏。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
                return;

                //if (tvWorkDirectory.Items.Count <= 0) return;
                //wtvi = tvWorkDirectory.Items[0] as WorkspaceTreeViewItem;
            }
            else
            {
                wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            }

            string directoryPath;
            WorkspaceTreeViewItem destWtvi;

            if (wtvi.IsMarkdownFilePath)
            {
                directoryPath = wtvi.ParentDirectory;
                destWtvi = wtvi.ParentWorkspaceTreeViewItem;
            }
            else
            {
                directoryPath = wtvi.FullPath;
                destWtvi = wtvi;
            }

            if (Directory.Exists(directoryPath) == false)
            {
                var lastIndex = directoryPath.LastIndexOf("\\");
                if (lastIndex < 0)
                {
                    LMessageBox.Show("　　只能在目录下新建文件。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                var parentDirectory = directoryPath.Substring(0, lastIndex);
                if (Directory.Exists(parentDirectory) == false)
                {
                    LMessageBox.Show("　　只能在目录下新建文件。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                directoryPath = parentDirectory;
            }

            if (directoryPath.EndsWith("~") || directoryPath.EndsWith("~\\"))
            {
                LMessageBox.Show("　　以波形符结尾的目录是程序自动添加的资源目录，不能在其中再建立MarkDown文件。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (directoryPath.EndsWith("\\") == false) directoryPath += "\\";

            string newShortFileName;

            if (isMetaFile)
            {
                newShortFileName = "_" + new DirectoryInfo(directoryPath).Name + ".md";
            }
            else
            {
                newShortFileName = InputBox.Show(Globals.AppName, "请输入新文件名：", "", true,
                   "说明：\r\n　　⑴不需要输入后缀名；\r\n　　⑵请尽可能设定有意义的文件名。\r\n　　⑶不能以下划线开头。\r\n　　因为此文件名很可能将来被其它文件引用。所以，如非必要，创建之后请勿随意更改文件名。");
            }

            if (newShortFileName == null) return;//用户放弃新建文件。

            if (newShortFileName.ToLower().EndsWith(".md") == false) newShortFileName += ".md";

            if (string.IsNullOrEmpty(newShortFileName))
            {
                LMessageBox.Show("　　文件名称不能为空！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            try
            {
                var newFilePath = directoryPath + newShortFileName;
                using (StreamWriter sw = File.CreateText(newFilePath))
                {
                    if (isSampleFile)
                    {
                        sw.Write(Properties.Resources.example);
                    }
                    else
                    {
                        var docTitle = newShortFileName.EndsWith(".md") ? newShortFileName.Substring(0, newShortFileName.Length - 3) : newShortFileName;
                        sw.Write($"\r\n%{FormatDocumentTitle(docTitle)}\r\n\r\n；{DateTime.Now.ToString()}\r\n\r\n");
                    }
                }

                WorkspaceTreeViewItem newtvi = new WorkspaceTreeViewItem(newFilePath, Globals.MainWindow);
                if (isMetaFile == false)
                {
                    //destWtvi.Items.Add(newtvi);

                    List<WorkspaceTreeViewItem> tmpItems = new List<WorkspaceTreeViewItem>();
                    int baseIndex = 0;
                    foreach (var item in destWtvi.Items)
                    {
                        var wi = item as WorkspaceTreeViewItem;
                        if (wi != null)
                        {
                            if (wi.ShortName.EndsWith("~"))//波型符结尾的总是资源文件夹。
                            {
                                baseIndex++;
                                continue;
                            }
                            tmpItems.Add(wi);
                        }
                    }
                    tmpItems.Add(newtvi);
                    tmpItems.Sort(new WorkspaceTreeViewItemCompare());
                    var index = tmpItems.IndexOf(newtvi) + baseIndex;
                    destWtvi.Items.Insert(index, newtvi);
                }//元文件不显示。

                OpenDocuments(new string[] { newFilePath });

                //TODO: 仍然无法实现使编辑器获取焦点
                FocusActiveEditor();
            }
            catch (Exception ex)
            {
                LMessageBox.Show("　　新建文件失败！错误消息：\r\n" + ex.Message + "\r\n" + ex.StackTrace,
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        /// <summary>
        /// 将字符串开头的数字去除。例如：1－2－3－xxx，格式化后只保留xxx。但如果只剩下空格，就返回原始值。
        /// </summary>
        /// <param name="src">源文本。</param>
        /// <returns>返回去除了数字前缀的文件短名。</returns>
        public static string FormatDocumentTitle(string src)
        {
            if (string.IsNullOrWhiteSpace(src)) return string.Empty;

            if (src.ToLower().EndsWith(".html"))
            {
                src = src.Substring(0, src.Length - 5);
            }

            if (src.ToLower().EndsWith(".md"))
            {
                src = src.Substring(0, src.Length - 3);
            }

            Regex regex = new Regex(@"^[ 　\t0123456789１２３４５６７８９０\-_]*");
            var match = regex.Match(src);
            string result = null;
            if (match.Success)
                result = src.Substring(match.Length);

            if (string.IsNullOrWhiteSpace(result))
            {
                return src;
            }

            return result;
        }

        /// <summary>
        /// 删除工作区管理器中某个文件或目录。
        /// </summary>
        private void miDeleteFile_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("　　请先选中要删除的文件。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;

            if (wtvi.FullPath.EndsWith("~") || wtvi.FullPath.EndsWith("~\\") || wtvi.FullPath == Globals.PathOfWorkspace)
            {
                LMessageBox.Show("　　这个文件或目录是程序自动生成的，不能直接删除。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }
            try
            {
                if (Directory.Exists(wtvi.FullPath))
                {
                    var result = LMessageBox.Show("　　删除目录会删除其中的所有文件和子目录。真的要删除此目录吗？", Globals.AppName,
                        MessageBoxButton.YesNo, MessageBoxImage.Warning);
                    if (result != MessageBoxResult.Yes) return;

                    Directory.Delete(wtvi.FullPath, true);
                    workspaceManager.Refresh(null);
                    return;
                }

                if (File.Exists(wtvi.FullPath))
                {
                    if (wtvi.IsImageFileExist)
                    {
                        ImagePreview.Source = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/App.png"));
                        File.Delete(wtvi.FullPath);

                        var parentItem = (wtvi.Parent as TreeViewItem);
                        parentItem.Items.Remove(wtvi);
                        parentItem.IsSelected = true;
                    }
                    else if (wtvi.IsMarkdownFilePath)
                    {
                        try
                        {
                            DirectoryInfo resourceDirectoryInfo = new DirectoryInfo(wtvi.ResourceDirectoryFullPath);

                            MarkdownEditor openedEditor = null;
                            foreach (var item in this.mainTabControl.Items)
                            {
                                var editor = item as MarkdownEditor;
                                if (editor == null) continue;

                                if (editor.FullFilePath.ToLower() == wtvi.FullPath.ToLower())
                                {
                                    openedEditor = editor;
                                    break;
                                }
                            }

                            if (openedEditor != null)
                            {
                                this.mainTabControl.Items.Remove(openedEditor);
                            }

                            File.Delete(wtvi.FullPath);

                            if (Directory.Exists(resourceDirectoryInfo.FullName))
                            {
                                Directory.Delete(resourceDirectoryInfo.FullName, true);
                            }

                            var parentItem = (wtvi.Parent as TreeViewItem);
                            parentItem.Items.Remove(wtvi);
                            parentItem.IsSelected = true;
                        }
                        catch (Exception ex)
                        {
                            LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 将当前 Markdown 文件、Image 文件、目录元文件的链接插入到当前活动编辑器的插入点位置处。
        /// </summary>
        private void miInsertLinkToCurrentDocument_Click(object sender, RoutedEventArgs e)
        {
            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (wtvi == null)
            {
                LMessageBox.Show("请先在工作区目录中选择一个文件作为要链接的目标文件！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var linkSourceFullPath = wtvi.FullPath;

            if (wtvi.IsMarkdownFilePath == false)
            {
                if (wtvi.IsImageFileExist)
                {
                    wtvi.InsertImageTagToDocument(wtvi.FullPath, true);
                    return;
                }
                else
                {
                    if (wtvi.FullPath.ToLower() == Globals.PathOfWorkspace.ToLower())
                    {
                        LMessageBox.Show("不能将工作区根目录作为链接插入到当前文档中！",
                            Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        return;
                    }
                    else
                    {
                        var di = new DirectoryInfo(wtvi.FullPath);
                        var directoryMdFileFullName = (di.FullName.EndsWith("\\") ? di.FullName : (di.FullName + "\\")) + "_" + di.Name + ".md";
                        linkSourceFullPath = directoryMdFileFullName;
                        CreateDirectoryMetaMdFile(di, directoryMdFileFullName);

                        InsertLinkToActiveDocument(wtvi, linkSourceFullPath);
                        return;
                    }
                }
            }

            InsertLinkToActiveDocument(wtvi, linkSourceFullPath);
        }

        /// <summary>
        /// 在当前活动编辑器的插入点位置添加对工作区管理器中当前选定的 Markdown 文件的链接。
        /// </summary>
        /// <param name="wtvi">工作区管理器中表示某个 Markdown 文件或某个目录的条目。（如果是目录，添加的应是其对应的元文件。</param>
        /// <param name="linkSourceFullPath">源文件路径，如果是目录，应传入目录元文件的路径。</param>
        private void InsertLinkToActiveDocument(WorkspaceTreeViewItem wtvi, string linkSourceFullPath)
        {
            //将当前选定文档作为一个链接插入到正在编辑的文档中。
            var editor = Globals.MainWindow.ActivedEditor;
            if (editor == null)
            {
                LMessageBox.Show("当前没有打开任何文档，无法添加对此文件的引用！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var destFilePath = editor.FullFilePath;
            var srcPath = linkSourceFullPath;//wtvi.FullPath.ToLower();
            string shortName;

            if (File.Exists(wtvi.FullPath))
            {
                FileInfo fi = new FileInfo(wtvi.FullPath);
                shortName = fi.Name.Substring(0, fi.Name.Length - fi.Extension.Length);
            }
            else if (Directory.Exists(wtvi.FullPath))
            {
                DirectoryInfo di = new DirectoryInfo(wtvi.FullPath);
                shortName = di.Name;
            }
            else shortName = "";

            editor.EditorBase.SelectedText = BuildLinkText(srcPath, destFilePath, ref shortName);
            var destSel = editor.EditorBase.SelectionStart + 1;
            if (destSel < editor.EditorBase.Document.TextLength)
            {
                editor.EditorBase.Select(destSel, Math.Max(FormatDocumentTitle(shortName).Length, 0));
            }
        }

        /// <summary>
        /// 按照工作区管理器中选定的 Markdown 文件条目查找所有对此文件的链接。
        /// </summary>
        private void miFindAllLinkToThisFile_Click(object sender, RoutedEventArgs e)
        {
            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (wtvi == null)
            {
                LMessageBox.Show("请先在工作区目录中选择一个文件作为要查找的文件！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (wtvi.IsMarkdownFilePath == false && wtvi.IsImageFileExist == false)
            {
                LMessageBox.Show("　　只有Markdown文件、图片资源文件才能链接（且需要编译为Html文档才有效）！",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            tvFindAndReplace.Items.Clear();

            FileInfo fi = new FileInfo(wtvi.FullPath);
            var shortName = fi.Name.Substring(0, fi.Name.Length - fi.Extension.Length);
            var item = new FindDocumentTreeViewItem(wtvi.FullPath, wtvi.ShortName, $"{wtvi.ShortName} 被引用于：");

            FindAllLinksToThisFile(item, fi.FullName, Globals.PathOfWorkspace, shortName);

            if (item.HasItems)
            {
                tvFindAndReplace.Items.Add(item);
                item.IsExpanded = true;
            }
            else
            {
                tvFindAndReplace.Items.Add(new TreeViewItem() { Header = "<没有找到任何引用...>" });
            }

            rightToolBar.SelectedIndex = 1;
        }

        /// <summary>
        /// 查找所有对此文件的引用。
        /// </summary>
        /// <param name="item">将查找结果放到此项目下级。</param>
        /// <param name="srcPath">查找的路径。</param>
        /// <param name="destDirectory">目的目录路径。</param>
        /// <param name="shortName">短名。</param>
        private void FindAllLinksToThisFile(FindDocumentTreeViewItem item, string srcPath, string destDirectory, string shortName)
        {
            if (File.Exists(srcPath) == false) return;

            if (Directory.Exists(destDirectory) == false) return;

            var directoryInfo = new DirectoryInfo(destDirectory);
            var files = directoryInfo.GetFiles();

            foreach (var file in files)
            {
                if (file.Extension.ToLower() != ".md" &&
                   WorkspaceTreeViewItem.IsValidateImageFilePath(file.FullName) == false) continue;

                var linkText = BuildLinkText(srcPath, file.FullName, ref shortName);
                var mIndex = linkText.IndexOf("](");
                if (linkText.StartsWith("[") && linkText.EndsWith(")") &&
                    (mIndex >= 1 && mIndex < linkText.Length - 2))
                {
                    linkText = linkText.Substring(mIndex + 1, linkText.Length - mIndex - 1);
                }
                //BuildLinkText()其实只针对文字链接，但稍加修改也可以用于图像文本链接，
                //因为图像文件链接只在头部多个！号而已。

                var edit = GetOpenedEditor(file.FullName);
                string[] lines;
                if (edit != null)
                {
                    //打开的文件，以正在编辑的内容为准
                    lines = edit.EditorBase.Text.Replace("\r", "").Split(new char[] { '\n' }, StringSplitOptions.None);
                }
                else
                {
                    //没打开的文件，以磁盘数据为准
                    lines = File.ReadAllLines(file.FullName);
                }

                var lineNumber = 0;
                foreach (var line in lines)
                {
                    lineNumber++;
                    Regex regex = new Regex(@"!?\[.*\](.*)");
                    var match = regex.Match(line);
                    if (match.Success)
                    {
                        var destLine = match.Value.ToLower();
                        var srcLine = linkText.ToLower();

                        bool isLinked = destLine.Contains(srcLine);
                        if (isLinked == false)
                        {
                            if (destLine.Contains("](./"))
                            {
                                if (srcLine.StartsWith("(./") == false)
                                {
                                    srcLine = "(./" + srcLine.Substring(1);
                                    isLinked = destLine.Contains(srcLine);
                                }
                            }
                            else
                            {
                                if (srcLine.StartsWith("(./"))
                                {
                                    srcLine = "(" + srcLine.Substring(3);
                                    isLinked = destLine.Contains(srcLine);
                                }
                            }
                        }

                        if (isLinked)
                        {
                            item.Items.Add(new FindLineTreeViewItem(file.FullName, file.Name, lineNumber, match.Index, match.Length,
                                $"文件：{file.Name}，第 {lineNumber} 行", null, FindLineTreeViewItem.ItemType.Normal));
                        }
                    }
                }
            }

            var subDirectories = directoryInfo.GetDirectories();
            foreach (var subDirectory in subDirectories)
            {
                if (subDirectory.FullName.EndsWith("~")) continue;

                FindAllLinksToThisFile(item, srcPath, subDirectory.FullName, subDirectory.Name);
            }
        }

        /// <summary>
        /// 取引用链接文本。
        /// </summary>
        /// <param name="resourceFullPathName">是指将被引用的资源的文件路径</param>
        /// <param name="mdFileFullPathName">resourceFullPathName。</param>
        /// <param name="shortName">资源短名，通常是文件名。</param>
        /// <returns>生成的用于插入到<para>mdFileFullPathName</para>指向的Md文件中的链接的文本。</returns>
        private string BuildLinkText(string resourceFullPathName, string mdFileFullPathName, ref string shortName)
        {
            //if (resourceFullPathName != null)
            //{
            //    resourceFullPathName = resourceFullPathName.ToLower();
            //}

            //if (mdFileFullPathName != null)
            //{
            //    mdFileFullPathName = mdFileFullPathName.ToLower();
            //}

            if (resourceFullPathName.EndsWith(".md"))
            {
                resourceFullPathName = resourceFullPathName.Substring(0, resourceFullPathName.Length - 3) + ".html";
            }

            var workspacePath = Globals.PathOfWorkspace.ToLower();

            //思路：先找出共同的、最接近的祖先级目录
            var separator = new char[] { '\\', '/' };
            string[] src = resourceFullPathName.Split(separator, StringSplitOptions.RemoveEmptyEntries);
            string[] dest = mdFileFullPathName.Split(separator, StringSplitOptions.RemoveEmptyEntries);

            var minIndex = Math.Min(src.Length, dest.Length) - 1;

            if (minIndex < 0) return "";

            int aIndex = -1;
            for (int i = 0; i <= minIndex; i++)
            {
                if (src[i].ToLower() != dest[i].ToLower())
                {
                    aIndex = i;
                    break;
                }
            }

            //生成相对路径的前半部分，类似（../../）这样子。
            string[] file = mdFileFullPathName.Split(separator, StringSplitOptions.RemoveEmptyEntries);
            string header;
            if (resourceFullPathName == mdFileFullPathName || aIndex < 0)//同一文件内部引用
            {
                header = "";
            }
            else
            {
                header = FindLineTreeViewItem.BuildHtmlRefText(file.Length - aIndex - 1);

                for (int i = aIndex; i < src.Length; i++)
                {
                    header += src[i];
                    header += "/";
                }

                header = header.Replace(".md", ".html");
                if (header.EndsWith("/"))
                {
                    header = header.Substring(0, header.Length - 1);
                }
            }

            if (shortName.EndsWith(".md"))
            {
                shortName = shortName.Substring(0, shortName.Length - 3);
            }

            var linkText = "[" + FormatDocumentTitle(shortName) + "](" + header + ")";
            return linkText;
        }

        /// <summary>
        /// 在工作区管理器中创建子目录。
        /// </summary>
        private void miNewDirectory_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("　　请先选中要在哪个目录下新建子目录。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (Directory.Exists(wtvi.FullPath) == false)
            {
                LMessageBox.Show("　　只能在目录下新建子目录。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            DirectoryInfo di = new DirectoryInfo(wtvi.FullPath);
            if (di.Name.EndsWith("~"))
            {
                LMessageBox.Show("　　以波形符结尾的目录是程序自动管理的资源目录，不允许在其下新建子目录。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var newShortDirectoryName = InputBox.Show(Globals.AppName, "请输入新目录名：", "", true);
            if (string.IsNullOrEmpty(newShortDirectoryName))
            {
                LMessageBox.Show("　　目录名称不能为空！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            try
            {
                var newFullPath = (wtvi.FullPath.EndsWith("\\") ? wtvi.FullPath : (wtvi.FullPath + "\\")) + newShortDirectoryName;
                if (Directory.Exists(newFullPath) == false)
                {
                    Directory.CreateDirectory(newFullPath);
                }
                var newItem = new WorkspaceTreeViewItem(newFullPath, Globals.MainWindow);
                //wtvi.Items.Add(newItem);

                //解决索引问题，判断在哪里插入。
                List<WorkspaceTreeViewItem> tmpItems = new List<WorkspaceTreeViewItem>();
                int baseIndex = 0;
                foreach (var item in wtvi.Items)
                {
                    var wi = item as WorkspaceTreeViewItem;
                    if (wi != null)
                    {
                        if (wi.ShortName.EndsWith("~"))//波型符结尾的总是资源文件夹。
                        {
                            baseIndex++;
                            continue;
                        }
                        tmpItems.Add(wi);
                    }
                }
                tmpItems.Add(newItem);
                tmpItems.Sort(new WorkspaceTreeViewItemCompare());
                var index = tmpItems.IndexOf(newItem) + baseIndex;
                wtvi.Items.Insert(index, newItem);

                newItem.IsSelected = true;
            }
            catch (Exception ex)
            {
                LMessageBox.Show("　　创建目录失败！错误消息：\r\n" + ex.Message + "\r\n" + ex.StackTrace,
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);

            }
        }

        /// <summary>
        /// 切换活动文档时自动对焦，以便直接输入文本。
        /// </summary>
        private void mainTabControl_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
        {
            if (mainTabControl.SelectedItem == null) return;
            MarkdownEditor me = mainTabControl.SelectedItem as MarkdownEditor;
            if (me == null) return;

            me.EditorBase.Focus();
        }

        /// <summary>
        /// 选择主题来编译 Html 文件。
        /// </summary>
        private void cmbColor_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (configManager == null || cmbColor.SelectedItem == null) return;

            var colorName = (cmbColor.SelectedItem as ComboBoxItem).Tag.ToString();
            configManager.Set("color", colorName);

            if (colorName == "light")
            {
                imagePreviewOutBorder.Background = Brushes.White;
            }
            else if (colorName == "dark")
            {
                imagePreviewOutBorder.Background = new SolidColorBrush(Color.FromRgb(0x3D, 0x3D, 0x3D));
            }
            else
            {
                imagePreviewOutBorder.Background = Brushes.Transparent;
            }
        }

        /// <summary>
        /// 在工作区管理器中重命名文件或文件夹。
        /// </summary>
        private void miRename_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("　　请先选中要重命名的项目。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;

            var result = LMessageBox.Show("　　重命名文件（或目录）会导致所有对此文件（或目录）的引用（链接）失效。\r\n　　" +
                "> 对与此文件相关的资源文件的引用也会失效。\r\n\r\n" +
                "　　建议先使用“查找引用”功能看看此文件（或此目录）是否被引用过。\r\n\r\n" +
                "　　要自动查找一下可能存在的引用么？（要注意可能出现同名文件被引用！）",
                Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning, "NPA_WarningBeforeRename");
            //NPA_ means No Prompt Again.

            if (result == MessageBoxResult.Yes)
            {
                cmbFindText.Text = wtvi.ShortName;
                cmbSearchArea.SelectedIndex = 2;
                FindText(tvFindAndReplace);
                return;
            }

            if (wtvi.IsDirectoryExists)
            {
                if (wtvi.FullPath == Globals.PathOfWorkspace)
                {
                    LMessageBox.Show("　　不能重命名工作区目录！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                if (wtvi.FullPath.EndsWith("~") || wtvi.FullPath.EndsWith("~\\"))
                {
                    LMessageBox.Show("　　这是程序预定的资源目录名，不可修改！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                var newShortName = InputBox.Show(Globals.AppName, "请输入新目录名称：", wtvi.ShortName, true);
                if (string.IsNullOrEmpty(newShortName))
                {
                    LMessageBox.Show("　　指定的目录名不合法。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                try
                {
                    #region 在重命名一个目录之前，先将该目录对应的元文件、元文件资源文件夹、编译后的html元文件重命名。
                    var oldFullPath = wtvi.FullPath;
                    if (oldFullPath.EndsWith("\\") == false) oldFullPath += "\\";

                    var newFullPath = wtvi.FullPath;
                    if (newFullPath.EndsWith("\\")) newFullPath = newFullPath.Substring(0, newFullPath.Length - 1);

                    string oldShortName = "";
                    var lastindex = newFullPath.LastIndexOf("\\");
                    if (lastindex >= 0)
                    {
                        oldShortName = newFullPath.Substring(lastindex + 1);
                        newFullPath = newFullPath.Substring(0, lastindex);
                    }

                    var oldMetaFileShortName = "_" + oldShortName + ".md";
                    var oldMetaFileFullPath = oldFullPath + oldMetaFileShortName;
                    var oldMetaFileResourceDirectoryPath = oldFullPath + "_" + oldShortName + "~\\";
                    var oldMetaHtmlFileFullPath = oldFullPath + "_" + oldShortName + ".html";

                    if (File.Exists(oldMetaFileFullPath))
                        File.Move(oldMetaFileFullPath, oldFullPath + "_" + newShortName + ".md");

                    if (Directory.Exists(oldMetaFileResourceDirectoryPath))
                        Directory.Move(oldMetaFileResourceDirectoryPath, oldFullPath + "_" + newShortName + "~");

                    if (File.Exists(oldMetaHtmlFileFullPath))
                        File.Move(oldMetaHtmlFileFullPath, oldFullPath + "_" + newShortName + ".html");

                    #endregion

                    //然后再命名文件夹本身。
                    newFullPath = newFullPath + "\\" + newShortName + "\\";
                    Directory.Move(wtvi.FullPath, newFullPath);
                    wtvi.FullPath = newFullPath;

                    //重命名打开的元文件。
                    foreach (var ue in this.mainTabControl.Items)
                    {
                        MarkdownEditor editor = ue as MarkdownEditor;
                        if (editor == null || editor.FullFilePath == null) continue;

                        if (editor.FullFilePath.ToLower().StartsWith(oldFullPath.ToLower()))
                        {
                            var shortName = editor.ShortFileName;
                            if (shortName == null) continue;
                            if (shortName.StartsWith("_"))
                            {
                                //元文件，要特别处理
                                if (oldFullPath.EndsWith("\\")) oldFullPath = oldFullPath.Substring(0, oldFullPath.Length - 1);
                                var lastI = oldFullPath.LastIndexOf("\\");
                                if (lastI >= 0)
                                {
                                    var s = oldFullPath.Substring(lastI + 1);
                                    if (("_" + s + ".md").ToLower() == shortName.ToLower())
                                    {
                                        editor.FullFilePath = newFullPath + "_" + newShortName + ".md";
                                        continue;
                                    }
                                }
                            }

                            editor.FullFilePath = newFullPath + editor.FullFilePath.Substring(oldFullPath.Length);
                        }
                    }
                }
                catch (Exception ex)
                {
                    LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }
            }
            else if (wtvi.IsFileExists)
            {
                var newShortName = InputBox.Show(Globals.AppName, "请输入新文件名：", wtvi.ShortName, true);
                if (newShortName == null) return;

                if (newShortName == "" || newShortName.ToLower() == ".md")
                {
                    LMessageBox.Show("　　指定的文件名不能为空。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                if (newShortName.ToLower().EndsWith(".md"))
                {
                    newShortName = newShortName.Substring(0, newShortName.Length - 3);
                }

                try
                {
                    var newFullPath = wtvi.FullPath;
                    if (newFullPath.EndsWith("\\")) newFullPath = newFullPath.Substring(0, newFullPath.Length - 1);

                    var lastindex = newFullPath.LastIndexOf("\\");
                    if (lastindex >= 0) newFullPath = newFullPath.Substring(0, lastindex);

                    newFullPath = newFullPath + "\\" + newShortName;

                    if (wtvi.IsMarkdownFilePath && newFullPath.ToLower().EndsWith(".md") == false) newFullPath += ".md";

                    File.Move(wtvi.FullPath, newFullPath);
                    var oldShortName = wtvi.ShortName;

                    var oldFilePath = wtvi.FullPath;
                    wtvi.FullPath = newFullPath;

                    //更新打开的文档的对应编辑器的FullPath
                    foreach (var item in Globals.MainWindow.mainTabControl.Items)
                    {
                        MarkdownEditor me = item as MarkdownEditor;
                        if (me == null) continue;

                        if (me.FullFilePath == oldFilePath)
                        {
                            me.FullFilePath = newFullPath;
                            break;
                        }
                    }

                    if (wtvi.FullPath.ToLower().EndsWith(".md"))
                    {
                        var parentDirectoryPath = wtvi.FullPath.Substring(0, wtvi.FullPath.LastIndexOf("\\"));
                        if (parentDirectoryPath.EndsWith("\\") == false) parentDirectoryPath += "\\";

                        var directories = new DirectoryInfo(parentDirectoryPath).GetDirectories();
                        foreach (var directory in directories)
                        {
                            var directoryShortName = oldShortName.Substring(0, oldShortName.Length - 3);
                            if (directory.FullName.EndsWith(directoryShortName) || directory.FullName.EndsWith(directoryShortName + "~"))
                            {
                                var dest = directory.FullName;
                                if (dest.EndsWith("\\")) dest.Substring(0, dest.Length - 1);

                                int lastIndex = dest.LastIndexOf("\\");
                                dest = dest.Substring(0, lastindex + 1);

                                directory.MoveTo(dest + newShortName + "~\\");
                                break;
                            }
                        }

                        var htmlFilePath = oldFilePath.Substring(0, oldFilePath.Length - 3) + ".html";

                        if (File.Exists(htmlFilePath))
                        {
                            File.Copy(htmlFilePath, parentDirectoryPath + newShortName + ".html");
                        }
                    }
                }
                catch (Exception ex)
                {
                    LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }
            }

            workspaceManager.Refresh(wtvi.FullPath);
        }

        /// <summary>
        /// 工作区管理器的快捷键处理。
        /// </summary>
        private void tvWorkDirectory_KeyDown(object sender, KeyEventArgs e)
        {
            switch (e.Key)
            {
                case Key.Delete:
                    {
                        miDeleteFile_Click(sender, e);
                        e.Handled = true;
                        break;
                    }
                case Key.Enter:
                    {
                        if (tvWorkDirectory.SelectedItem != null)
                        {
                            var wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
                            if (wtvi != null)
                            {
                                if (wtvi.IsMarkdownFilePath)
                                {
                                    wtvi.OpenFile();
                                    e.Handled = true;
                                }
                                else if (wtvi.IsDirectoryExists)
                                {
                                    wtvi.IsExpanded = true;
                                    e.Handled = true;
                                }
                                else if (wtvi.IsImageFileExist)
                                {
                                    wtvi.InsertImageTagToDocument(wtvi.FullPath);
                                    e.Handled = true;
                                }
                            }
                        }
                        break;
                    }
                case Key.F2:
                    {
                        miRename_Click(sender, e);
                        break;
                    }
            }
        }

        /// <summary>
        /// 格式化活动编辑器中的 Markdown 文本。
        /// </summary>
        private void miFormatDocument_Click(object sender, RoutedEventArgs e)
        {
            Format();
        }

        /// <summary>
        /// 格式化活动编辑器中的 Markdown 文本。
        /// </summary>
        private void Format()
        {
            if (this.mainTabControl.SelectedItem == null) return;

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            eti.EditorBase.Format();
        }

        /// <summary>
        /// 根据路径查找工作区管理器中有无此条目。
        /// </summary>
        /// <param name="fullPath">要找的文件或目录的路径。</param>
        /// <returns>返回工作区管理器中某符合条件的条目。</returns>
        public WorkspaceTreeViewItem FindWorkspaceTreeViewItem(string fullPath)
        {
            if (string.IsNullOrEmpty(fullPath)) return null;

            if (IsMetaFilePath(fullPath))
            {
                FileInfo fi = new FileInfo(fullPath);
                fullPath = fi.DirectoryName;
                if (fullPath.EndsWith("\\") == false)
                {
                    fullPath += "\\";
                }
            }

            var baseItem = tvWorkDirectory.Items[0] as WorkspaceTreeViewItem;//根节点，必然存在。

            if (baseItem.FullPath.ToLower() == fullPath.ToLower()) return baseItem;

            foreach (var item in baseItem.Items)
            {
                WorkspaceTreeViewItem wtvi = item as WorkspaceTreeViewItem;
                if (wtvi == null) continue;

                if (wtvi.FullPath.ToLower() == fullPath.ToLower()) return wtvi;

                var resultItem = SearchWorkspaceTreeViewItem(wtvi, fullPath);
                if (resultItem != null) return resultItem;
            }

            return null;
        }

        /// <summary>
        /// 判断是否目录元文件。
        /// </summary>
        /// <param name="fullPath">文件路径。</param>
        /// <returns>以“_”开头的“.md”文件即返回真。</returns>
        private bool IsMetaFilePath(string fullPath)
        {
            try
            {
                FileInfo fi = new FileInfo(fullPath);
                if (fi.Name.StartsWith("_") && fi.Name.ToLower().EndsWith(".md"))
                {
                    return true;
                }

                return false;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// 根据指定路径查找工作区管理器中对应的条目。
        /// </summary>
        /// <param name="item">从此条目开始查找。</param>
        /// <param name="fullPath">要查找的条目指向的磁盘路径。</param>
        /// <returns>返回找到的条目。</returns>
        private WorkspaceTreeViewItem SearchWorkspaceTreeViewItem(WorkspaceTreeViewItem item, string fullPath)
        {
            if (item == null || string.IsNullOrEmpty(fullPath)) return null;

            if (item.FullPath.ToLower() == fullPath.ToLower()) return item;

            foreach (var childItem in item.Items)
            {
                WorkspaceTreeViewItem wtvi = childItem as WorkspaceTreeViewItem;
                if (wtvi == null) continue;

                var resultItem = SearchWorkspaceTreeViewItem(wtvi, fullPath);
                if (resultItem != null) return resultItem;
            }

            return null;
        }

        /// <summary>
        /// 格式化当前活动编辑器当前编辑区附近的文字表。
        /// </summary>
        private void miFormatTextTable_Click(object sender, RoutedEventArgs e)
        {
            FormatAsTextTable();
        }

        /// <summary>
        /// 格式化当前活动编辑器当前编辑区附近的文字表。
        /// </summary>
        private void FormatAsTextTable()
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            eti.FormatTextTable();
        }

        private XmlConfigManager workspaceConfigManager = null;
        /// <summary>
        /// 工作区配置文件管理器。
        /// </summary>
        public XmlConfigManager WorkspaceConfigManager
        {
            get
            {
                if (workspaceConfigManager == null)
                {
                    workspaceConfigManager = new XmlConfigManager(Globals.PathOfWorkspace + "Configure.xml");
                }

                return workspaceConfigManager;
            }
        }

        /// <summary>
        /// 更改当前工作区目录。
        /// </summary>
        /// <param name="newWorkspacePath">新工作区目录的路径。</param>
        public void ChangeWorkspace(string newWorkspacePath = null)
        {
            //检查文件保存状态
            try
            {
                List<MarkdownEditor> needSavingDocumentList = new List<MarkdownEditor>();
                foreach (var item in this.mainTabControl.Items)
                {
                    MarkdownEditor eti = item as MarkdownEditor;
                    if (eti != null && eti.IsModified)
                    {
                        needSavingDocumentList.Add(eti);
                    }
                }

                if (needSavingDocumentList.Count > 0)
                {
                    MessageBoxResult r = LMessageBox.Show(string.Format("　　有 {0} 个文档已被修改，要保存吗？", needSavingDocumentList.Count),
                        Globals.AppName, MessageBoxButton.YesNoCancel, MessageBoxImage.Warning);
                    switch (r)
                    {
                        case MessageBoxResult.Yes:
                            {
                                string saveInfo;
                                foreach (MarkdownEditor eti in needSavingDocumentList)
                                {
                                    saveInfo = eti.SaveDocument();
                                    if (saveInfo == "用户取消操作")
                                    {
                                        return;//不更改工作目录
                                    }
                                    else if (saveInfo != string.Empty)
                                    {
                                        LMessageBox.Show(saveInfo, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                                        return;//不更改工作目录
                                    }
                                }

                                this.mainTabControl.Items.Clear();
                                break;
                            }
                        case MessageBoxResult.Cancel: return;//不更改工作目录。
                        default://No,放弃保存，更改工作目录。
                            {
                                break;
                            }
                    }
                }

                //写入当前打开的文档列表。
                RememberOpenedFilesForWorkspace();

                this.mainTabControl.Items.Clear();
                this.workspaceConfigManager = null;//重置工作区配置文件管理器。

                string destWorkspaceFolder = newWorkspacePath;

                if (destWorkspaceFolder == null)
                {
                    //选择新工作目录
                    System.Windows.Forms.FolderBrowserDialog fbd = new System.Windows.Forms.FolderBrowserDialog()
                    {
                        Description = "　　请选择一个目录作为新的工作区目录（要求路径中不能含半角英文空格）。\r\n　　一些必要的css、js和图像文件会被复制到指定的目录中。",
                        ShowNewFolderButton = true,
                    };
                    System.Windows.Interop.HwndSource source = PresentationSource.FromVisual(this) as System.Windows.Interop.HwndSource;

                    System.Windows.Forms.IWin32Window win = new WinFormWindow(source.Handle);
                    System.Windows.Forms.DialogResult result = fbd.ShowDialog(win);
                    if (result.Equals(System.Windows.Forms.DialogResult.OK) && Directory.Exists(fbd.SelectedPath))
                    {
                        if (fbd.SelectedPath.Contains(" "))
                        {
                            var result2 = LMessageBox.Show("　　您选择的新目录的完整路径中含有空格，这会导致调用 Html Help Workshop 时出现一些小意外。\r\n　　您确定要这样做吗？",
                            Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning, "NPA_UseSpecialCharForFileName");
                            // NPA_ means No Prompt Again.

                            if (result2 != MessageBoxResult.Yes) return;
                        }

                        destWorkspaceFolder = fbd.SelectedPath;
                    }
                    else return;//相当于用户取消了操作。
                }
                else
                {
                    if (Directory.Exists(destWorkspaceFolder) == false)
                    {
                        var result = LMessageBox.Show("　　物理磁盘上不存在该目录，要创建一个新目录吗？", Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
                        if (result != MessageBoxResult.Yes) return;

                        if (destWorkspaceFolder.Contains(" "))
                        {
                            var result2 = LMessageBox.Show("　　目标目录的完整路径中含有空格，这会导致调用 Html Help Workshop 时出现一些小意外。\r\n　　你确定要这样做吗？",
                                                Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning, "NPA_UseSpecialCharForFileName");
                            if (result2 != MessageBoxResult.Yes) return;
                        }

                        System.IO.Directory.CreateDirectory(destWorkspaceFolder);
                    }
                }

                this.configManager.Set("Workspace", destWorkspaceFolder);
                Globals.PathOfWorkspace = destWorkspaceFolder;
                this.tbxPathOfWorkspace.Text = destWorkspaceFolder;

                CopyCssAndResourceFiles(destWorkspaceFolder);
                this.workspaceManager.Refresh(null);

                previewFrame.Source = null;

                //记载到一个列表文件中
                // var lines = File.ReadAllLines(Globals.PathOfHistoryWorkspaceFileFullName);
                WriteHistoryWorkspacesFile(destWorkspaceFolder);

                for (int i = lbxHistoryWorkspaces.Items.Count - 1; i >= 0; i--)
                {
                    var rwitem = lbxHistoryWorkspaces.Items[i] as RecentDirectoryListBoxItem;
                    if (rwitem != null && rwitem.DirectoryPath == destWorkspaceFolder)
                    {
                        lbxHistoryWorkspaces.Items.Remove(rwitem);
                    }
                }

                var rw = new RecentDirectoryListBoxItem(destWorkspaceFolder)
                {
                    ToolTip = "双击设置为当前工作区目录",
                };
                rw.MouseDoubleClick += rw_MouseDoubleClick;

                lbxHistoryWorkspaces.Items.Insert(0, rw);
                lbxHistoryWorkspaces.SelectedIndex = 0;

                tabControlLeftTools.SelectedIndex = 0;

                RefreshWorkspaceHistoryList();

                LoadRememberOpenedFiles();//载入上次打开的文件。
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
            }
        }

        /// <summary>
        /// 刷新历史工作区列表。
        /// </summary>
        private void RefreshWorkspaceHistoryList()
        {
            for (int i = 0; i < lbxHistoryWorkspaces.Items.Count; i++)
            {
                var item = lbxHistoryWorkspaces.Items[i] as RecentDirectoryListBoxItem;
                if (item == null) continue;

                item.IndexText = (i + 1).ToString();
            }
        }

        /// <summary>
        /// 载入上次关闭程序时打开的那些文件。需要开启“记住打开的文件”这个开关才应起作用。
        /// </summary>
        private void LoadRememberOpenedFiles()
        {
            if (this.RememberOpenedFiles == false) return;

            var openedFilesText = this.WorkspaceConfigManager.Get("OpenedFiles");
            var rememberedActiveDocumentFullPath = this.WorkspaceConfigManager.Get("ActiveDocumentFullPath");
            //文档初始化

            if (string.IsNullOrEmpty(openedFilesText) || this.RememberOpenedFiles == false) return;

            var openedFiles = openedFilesText.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);

            OpenDocuments(openedFiles);

            bool shouldFocusFstFile = true;

            var activeDocumentFullPath = this.WorkspaceConfigManager.Get("ActiveDocumentFullPath");
            if (File.Exists(activeDocumentFullPath))
            {
                foreach (var item in this.mainTabControl.Items)
                {
                    var me = item as MarkdownEditor;
                    if (me == null || me.FullFilePath == null) continue;

                    if (me.FullFilePath.ToLower() == activeDocumentFullPath.ToLower())
                    {
                        this.mainTabControl.SelectedItem = me;
                        shouldFocusFstFile = false;
                        break;
                    }
                }
            }

            if (shouldFocusFstFile && this.mainTabControl.Items.Count > 0)
            {
                this.mainTabControl.SelectedIndex = 0;
            }

            if (string.IsNullOrWhiteSpace(rememberedActiveDocumentFullPath) == false &&
                File.Exists(rememberedActiveDocumentFullPath))
            {
                int index = -1;
                for (int i = 0; i < this.mainTabControl.Items.Count; i++)
                {
                    var editor = this.mainTabControl.Items[i] as MarkdownEditor;
                    if (editor == null) continue;

                    if (editor.FullFilePath == rememberedActiveDocumentFullPath)
                    {
                        index = i;
                        break;
                    }
                }

                if (index >= 0)
                {
                    this.mainTabControl.SelectedIndex = index;
                }
            }
        }

        /// <summary>
        /// 重写“历史工作区列表”到文件中。会将新选中的工作区移动到历史工作区列表的头部。
        /// </summary>
        /// <param name="destWorkspaceFolder">新工作区目录的路径。</param>
        private static void WriteHistoryWorkspacesFile(string destWorkspaceFolder)
        {
            if (File.Exists(Globals.PathOfHistoryWorkspaceFileFullName))
            {
                string[] lines = File.ReadAllLines(Globals.PathOfHistoryWorkspaceFileFullName, new UnicodeEncoding());
                var newLinesList = new List<string>();
                foreach (var line in lines)
                {
                    if (line == destWorkspaceFolder) continue;

                    newLinesList.Add(line);
                }

                var newLines = new string[newLinesList.Count + 1];
                newLines[0] = destWorkspaceFolder;
                for (int i = 1; i < newLines.Length; i++)
                {
                    newLines[i] = newLinesList[i - 1];
                }

                File.WriteAllLines(Globals.PathOfHistoryWorkspaceFileFullName, newLines, new UnicodeEncoding());
            }
            else
            {
                File.WriteAllLines(Globals.PathOfHistoryWorkspaceFileFullName, new string[] { destWorkspaceFolder }, new UnicodeEncoding());
            }
        }

        /// <summary>
        /// 更改所有编辑器默认字体。
        /// </summary>
        private void fontFamilyList_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (fontFamilyList.SelectedItem == null) return;

            var item = fontFamilyList.SelectedItem as FontFamilyListItem;
            if (item == null) return;

            //右工具栏中的对照区不能直接对右工具栏变化字号
            foreach (var ue in this.rightToolBar.Items)
            {
                MarkdownEditor eti = ue as MarkdownEditor;
                if (eti == null) continue;

                eti.EditorBase.FontFamily = item.FontFamily;
            }

            this.mainTabControl.FontFamily = item.FontFamily;

            string fontEnName;
            if (item.FontFamily.FamilyNames.TryGetValue(System.Windows.Markup.XmlLanguage.GetLanguage("en-us"), out fontEnName))
            {
                this.configManager.Set("FontFamily", fontEnName);
            }
        }

        /// <summary>
        /// 显示帮助页。
        /// </summary>
        private void miHelp_Click(object sender, RoutedEventArgs e)
        {
            ShowHelp();
        }

        /// <summary>
        /// 显示帮助页。
        /// </summary>
        private void ShowHelp()
        {
            rightToolBar.SelectedIndex = 4;
            if (rightToolBar.Visibility != System.Windows.Visibility.Visible ||
                            rightToolBar.ActualWidth <= 0)
            {
                SwitchRightToolBarToggle();
            }
        }

        /// <summary>
        /// 显示/隐藏左工具栏。
        /// </summary>
        private void bdLeftToolsController_Click(object sender, RoutedEventArgs e)
        {
            SwitchLeftToolBarToggle();
        }

        /// <summary>
        /// 被折叠前左工具栏的宽度，最小值为20。
        /// </summary>
        private double leftToolBarWidth = 360;

        /// <summary>
        /// 显示/隐藏左工具栏。
        /// </summary>
        private void SwitchLeftToolBarToggle()
        {
            if (cdLeftToolsArea.ActualWidth > 0)
            {
                leftToolBarWidth = Math.Max(20, cdLeftToolsArea.ActualWidth);
                cdLeftToolsArea.Width = new GridLength(0, GridUnitType.Star);
            }
            else
            {
                cdLeftToolsArea.Width = new GridLength(leftToolBarWidth, GridUnitType.Pixel);
            }
        }

        /// <summary>
        /// 被折叠前右工具栏的宽度。
        /// </summary>
        private double rightToolBarWidth = 300;

        /// <summary>
        /// 显示/隐藏右工具栏。
        /// </summary>
        internal void SwitchRightToolBarToggle()
        {
            if (cdRightToolsArea.ActualWidth > 0)
            {
                rightToolBarWidth = Math.Max(20, cdRightToolsArea.ActualWidth);
                cdRightToolsArea.Width = new GridLength(0, GridUnitType.Star);
            }
            else
            {
                cdRightToolsArea.Width = new GridLength(rightToolBarWidth, GridUnitType.Pixel);
            }
        }

        /// <summary>
        /// 显示/隐藏“查找/替换”面板。
        /// </summary>
        internal void SwitchFindAndReplacePanelToggle()
        {
            if (rdFindAndReplace.ActualHeight <= 40)
            {
                rdFindAndReplace.Height = new GridLength(140, GridUnitType.Auto);
                cmbFindText.UpdateLayout();
            }
            else
            {
                rdFindAndReplace.Height = new GridLength(0);
            }
        }

        /// <summary>
        /// 显示、隐藏右工具栏。
        /// </summary>
        private void bdRightToolsController_Click(object sender, RoutedEventArgs e)
        {
            SwitchRightToolBarToggle();
        }

        /// <summary>
        /// 切换“保存前自动格式化Markdown文本”的开关。
        /// </summary>
        private void miFormatBeforeSave_Click(object sender, RoutedEventArgs e)
        {
            this.miFormatBeforeSave.IsChecked = !this.miFormatBeforeSave.IsChecked;
            this.configManager.Set("AutoFormat", miFormatBeforeSave.IsChecked.ToString());
        }

        private bool rememberOpenedFiles = false;
        /// <summary>
        /// 决定要不要下次启动时自动打开上次关闭时打开的那些文件。
        /// </summary>
        public bool RememberOpenedFiles
        {
            get { return rememberOpenedFiles; }
            set { rememberOpenedFiles = value; }
        }

        /// <summary>
        /// 切换“记住打开的文档”属性的值。如果为真，下次启动时会自动打开上次关闭时打开的那些文件。
        /// </summary>
        private void miRememberOpenedFiles_Click(object sender, RoutedEventArgs e)
        {
            miRememberOpenedFiles.IsChecked = !miRememberOpenedFiles.IsChecked;
            this.RememberOpenedFiles = miRememberOpenedFiles.IsChecked;
            this.configManager.Set("RememberOpenedFiles", miRememberOpenedFiles.IsChecked.ToString());
        }

        /// <summary>
        /// 显示帮助页。
        /// </summary>
        private void btnHelp_Click(object sender, RoutedEventArgs e)
        {
            ShowHelp();
        }

        private bool selectCellFirst = false;
        /// <summary>
        /// 编辑时按Tab键，决定是否选选中当前文字表单元格中所有文本。
        /// </summary>
        public bool SelectCellFirst
        {
            get { return selectCellFirst; }
            set { selectCellFirst = value; }
        }

        public enum HtmlHeadersCollapseType { Auto, Manual, No }

        private HtmlHeadersCollapseType htmlHeadersCollapse = MainWindow.HtmlHeadersCollapseType.Manual;
        /// <summary>
        /// 决定编译后的 Html 文档中的各标题是否自动折叠。
        /// </summary>
        public HtmlHeadersCollapseType HtmlHeadersCollapse
        {
            get { return htmlHeadersCollapse; }
            set { htmlHeadersCollapse = value; }
        }

        private bool autoNumberHeaders = true;
        /// <summary>
        /// 决定是否让编译后的 Html 文档中的六级标题都支持自动编号。
        /// </summary>
        public bool AutoNumberHeaders
        {
            get { return autoNumberHeaders; }
            set { autoNumberHeaders = value; }
        }

        private bool compileCodeToFillBlank = false;
        /// <summary>
        /// 决定是否将编译后的 Html 文档中的行内代码块初始色彩设置为透明色——这样就呈现出填空题的效果。
        /// </summary>
        public bool CompileCodeToFillBlank
        {
            get { return compileCodeToFillBlank; }
            set { compileCodeToFillBlank = value; }
        }

        private bool hideExamAnswer = true;
        /// <summary>
        /// 决定编译后的 Html 文档中的各试题是否隐藏答案。
        /// </summary>
        public bool HideExamAnswer
        {
            get { return hideExamAnswer; }
            set { hideExamAnswer = value; }
        }

        private bool textAutoWrap = true;
        /// <summary>
        /// 决定编辑器中的文本是否自动折行的开关。
        /// </summary>
        public bool TextAutoWrap
        {
            get { return this.textAutoWrap; }
            set
            {
                this.textAutoWrap = value;
                RefreshTextAutoWrap();
            }
        }

        /// <summary>
        /// 刷新各编辑器文本自动折行状态。
        /// </summary>
        private void RefreshTextAutoWrap()
        {
            foreach (var item in this.mainTabControl.Items)
            {
                var editor = item as MarkdownEditor;
                if (editor == null) continue;

                editor.EditorBase.WordWrap = this.textAutoWrap;
            }
        }

        private bool formatAfterCompile = false;
        /// <summary>
        /// 是否对编译后的Html文档进行格式化。
        /// </summary>
        public bool FormatAfterCompile
        {
            get { return this.formatAfterCompile; }
            set { this.formatAfterCompile = value; }
        }

        private bool isExamEnabled = true;
        /// <summary>
        /// 是否启用“试题编辑”功能。
        /// </summary>
        public bool IsExamEnabled
        {
            get
            {
                return isExamEnabled;
            }

            set
            {
                isExamEnabled = value;
                foreach (var i in mainTabControl.Items)
                {
                    var me = i as MarkdownEditor;
                    if (me == null) continue;

                    me.IsExamEnabled = value;
                }

                if (isExamEnabled)
                {
                    mExams.Visibility = Visibility.Visible;
                    tabValidateDocument.Visibility = Visibility.Visible;
                }
                else
                {
                    mExams.Visibility = Visibility.Collapsed;
                    tabValidateDocument.Visibility = Visibility.Collapsed;
                }
            }
        }

        private bool isAutoCompletionEnabled = false;
        /// <summary>
        /// 是否启用“自动完成”功能。
        /// </summary>
        public bool IsAutoCompletionEnabled
        {
            get
            {
                return isAutoCompletionEnabled;
            }

            set
            {
                isAutoCompletionEnabled = value;
                foreach (var i in mainTabControl.Items)
                {
                    var me = i as MarkdownEditor;
                    if (me == null) continue;

                    me.IsAutoCompletionEnabled = value;
                }
            }
        }

        private bool isEnToChineseDictEnabled = false;
        /// <summary>
        /// 英译中词典条目自动提示功能是否启用的开关。
        /// </summary>
        public bool IsEnToChineseDictEnabled
        {
            get
            {
                return isEnToChineseDictEnabled;
            }

            set
            {
                isEnToChineseDictEnabled = value;

                foreach (var i in mainTabControl.Items)
                {
                    var me = i as MarkdownEditor;
                    me.IsEnToChineseDictEnabled = value;
                }

                if (value)
                {
                    LoadDictionary();
                }
            }
        }

        private bool isShowSpaces = false;
        /// <summary>
        /// 决定是否显示空格。
        /// </summary>
        public bool IsShowSpaces
        {
            get { return isShowSpaces; }
            set
            {
                isShowSpaces = value;
                foreach (var i in mainTabControl.Items)
                {
                    var me = i as MarkdownEditor;
                    me.IsShowSpaces = value;
                }

                foreach (var i in rightToolBar.Items)
                {
                    var me = i as MarkdownEditor;
                    if (me != null) me.IsShowSpaces = value;
                }
            }
        }

        private bool isShowEndOfLine = false;
        /// <summary>
        /// 决定是否显示空格。
        /// </summary>
        public bool IsShowEndOfLine
        {
            get { return isShowEndOfLine; }
            set
            {
                isShowEndOfLine = value;
                foreach (var i in mainTabControl.Items)
                {
                    var me = i as MarkdownEditor;
                    me.IsShowEndOfLine = value;
                }

                foreach (var i in rightToolBar.Items)
                {
                    var me = i as MarkdownEditor;
                    if (me != null) me.IsShowEndOfLine = value;
                }
            }
        }

        private bool isShowTabs = false;
        /// <summary>
        /// 决定是否显示空格。
        /// </summary>
        public bool IsShowTabs
        {
            get { return isShowTabs; }
            set
            {
                isShowTabs = value;
                foreach (var i in mainTabControl.Items)
                {
                    var me = i as MarkdownEditor;
                    me.IsShowTabs = value;
                }

                foreach (var i in rightToolBar.Items)
                {
                    var me = i as MarkdownEditor;
                    if (me != null) me.IsShowTabs = value;
                }
            }
        }

        private bool showTitleInWorkspaceManager = false;
        /// <summary>
        /// 在工作区管理器中是显示文件短名还是显示文档中设置的“标题”文本。
        /// 文件短名利于看出顺序；标题则更美观些，更直观。
        /// </summary>
        public bool ShowTitleInWorkspaceManager
        {
            get { return showTitleInWorkspaceManager; }
            set
            {
                showTitleInWorkspaceManager = value;
                foreach (var item in tvWorkDirectory.Items)
                {
                    var ti = item as TreeViewItem;
                    if (ti != null)
                    {
                        RefreshWorkspaceTreeViewItemTitles(ti, value);
                    }
                }
            }
        }

        /// <summary>
        /// 刷新工作区管理器中所有项目的标题。
        /// </summary>
        public void RefreshWorkspaceTreeViewItemTitles(TreeViewItem item, bool value)
        {
            var wi = item as WorkspaceTreeViewItem;
            if (wi != null)
            {
                wi.ShowTitle = value;
            }

            foreach (var subItem in item.Items)
            {
                var sti = subItem as TreeViewItem;
                if (sti != null)
                {
                    RefreshWorkspaceTreeViewItemTitles(sti, value);
                }
            }
        }

        /// <summary>
        /// 载入词典条目。
        /// </summary>
        private void LoadDictionary()
        {
            dictionary.Clear();
            LoadDictionaryFromFile(Globals.PathOfUserDictionary);
            LoadDictionaryFromFile(Globals.PathOfWorkspaceDictionary);

            foreach (var i in this.mainTabControl.Items)
            {
                var edit = i as MarkdownEditor;
                if (edit == null) continue;

                edit.EditorBase.InvalidateCompletionWindow();
            }
        }

        /// <summary>
        /// 从指定词典文件中载入词典。
        /// </summary>
        /// <param name="filePath">词典文件路径。</param>
        private void LoadDictionaryFromFile(string filePath)
        {
            if (File.Exists(filePath) == false) return;

            using (StreamReader sr = new StreamReader(filePath))
            {
                var line = sr.ReadLine();
                var splitter = new char[] { '\t' };
                while (string.IsNullOrEmpty(line) == false)
                {
                    if (line.StartsWith(";"))//以分号开头的是注释行
                    {
                        line = sr.ReadLine();
                        continue;
                    }

                    EnToChEntry entry = new EnToChEntry();
                    string[] pieces = line.Split(splitter, StringSplitOptions.RemoveEmptyEntries);
                    int i = 0;
                    if (i < pieces.Length) entry.English = pieces[i];
                    i = 1;
                    if (i < pieces.Length) entry.Chinese = pieces[i];
                    i = 2;
                    if (i < pieces.Length) entry.Alpha = pieces[i];
                    i = 3;
                    if (i < pieces.Length) entry.Comment = pieces[i];
                    dictionary.Add(entry);
                    line = sr.ReadLine();
                }
            }
        }

        /// <summary>
        /// 切换“SelectCellFirst”属性的值。此属性的值将决定
        /// 在编辑文档时，按下Tab键的行为（在文字表中是选选中当前单元格所有文本还是直接跳转到后一单元格）。
        /// </summary>
        private void miSelectCellFirst_Click(object sender, RoutedEventArgs e)
        {
            miSelectCellFirst.IsChecked = !miSelectCellFirst.IsChecked;
            this.SelectCellFirst = miSelectCellFirst.IsChecked;
            this.configManager.Set("SelectCellFirst", miSelectCellFirst.IsChecked.ToString());
        }

        /// <summary>
        /// 切换“HtmlHeadersCollapse”属性的值。此属性的值将决定编译后的 Html 文件中的各标题是：“自动折叠”、“手动折叠”还是“不折叠”。
        /// </summary>
        private void miAutoCollapseHtmlHeaders_Click(object sender, RoutedEventArgs e)
        {
            miAutoCollapseHtmlHeaders.IsChecked = true;
            miManualCollapseHtmlHeaders.IsChecked = false;
            miNoCollapseHtmlHeaders.IsChecked = false;

            this.HtmlHeadersCollapse = HtmlHeadersCollapseType.Auto;
            this.configManager.Set("HtmlHeadersCollapse", this.HtmlHeadersCollapse.ToString());
        }

        /// <summary>
        /// 切换“HtmlHeadersCollapse”属性的值。此属性的值将决定编译后的 Html 文件中的各标题是：“自动折叠”、“手动折叠”还是“不折叠”。
        /// </summary>
        private void miManualCollapseHtmlHeaders_Click(object sender, RoutedEventArgs e)
        {
            miManualCollapseHtmlHeaders.IsChecked = true;
            miAutoCollapseHtmlHeaders.IsChecked = false;
            miNoCollapseHtmlHeaders.IsChecked = false;

            this.HtmlHeadersCollapse = HtmlHeadersCollapseType.Manual;
            this.configManager.Set("HtmlHeadersCollapse", this.HtmlHeadersCollapse.ToString());
        }

        /// <summary>
        /// 切换“HtmlHeadersCollapse”属性的值。此属性的值将决定编译后的 Html 文件中的各标题是：“自动折叠”、“手动折叠”还是“不折叠”。
        /// </summary>
        private void miNoCollapseHtmlHeaders_Click(object sender, RoutedEventArgs e)
        {
            miNoCollapseHtmlHeaders.IsChecked = true;
            miAutoCollapseHtmlHeaders.IsChecked = false;
            miManualCollapseHtmlHeaders.IsChecked = false;

            this.HtmlHeadersCollapse = HtmlHeadersCollapseType.No;
            this.configManager.Set("HtmlHeadersCollapse", this.HtmlHeadersCollapse.ToString());
        }

        /// <summary>
        /// 更改当前工作区目录。
        /// </summary>
        private void btnSetWorkspace_Click(object sender, RoutedEventArgs e)
        {
            ChangeWorkspace();
        }

        #region 搜索资源

        /// <summary>
        /// 在工作区管理器中查找指定的资源文件条目。
        /// </summary>
        private void tbxSearchResource_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                SearchCustomResources();
                e.Handled = true;
            }
        }

        /// <summary>
        /// 在工作区管理器中查找指定的资源文件条目。
        /// </summary>
        private void SearchCustomResources()
        {
            searchedResourceTreeviewItemList.Clear();
            searchResourceIndex = -1;

            this.cmbSearchResource.Items.Insert(0, cmbSearchResource.Text);

            if (SearchCustomResources(tvWorkDirectory.Items[0] as WorkspaceTreeViewItem, this.cmbSearchResource.Text))
            {
                if (searchedResourceTreeviewItemList.Count >= 0)
                {
                    searchResourceIndex += 1;
                    searchedResourceTreeviewItemList[searchResourceIndex].IsSelected = true;
                    ExpandWorkspaceTreeviewItem(searchedResourceTreeviewItemList[searchResourceIndex]);
                    tvWorkDirectory.Focus();
                }
            }

            if (tabControlLeftTools.SelectedIndex != 0)
                tabControlLeftTools.SelectedIndex = 0;
        }

        /// <summary>
        /// 在工作区管理器中查找指定的资源文件条目。
        /// </summary>
        private bool SearchCustomResources(WorkspaceTreeViewItem item, string findKeyWord)
        {
            if (string.IsNullOrEmpty(findKeyWord)) return false;

            if (item == null) return false;

            bool finded = false;

            if (string.IsNullOrEmpty(item.ShortName)) return false;

            if (item.ShortName.ToLower().Contains(findKeyWord.ToLower()))
            {
                searchedResourceTreeviewItemList.Add(item);
                finded = true;
            }

            if (item.Items.Count > 0)
            {
                foreach (var it in item.Items)
                {
                    var subFined = SearchCustomResources(it as WorkspaceTreeViewItem, findKeyWord);
                    finded = subFined || finded;
                }
            }

            return finded;
        }

        private List<WorkspaceTreeViewItem> searchedResourceTreeviewItemList = new List<WorkspaceTreeViewItem>();

        /// <summary>
        /// 用来在切换当前找到的资源时定位。
        /// </summary>
        private int searchResourceIndex = -1;

        /// <summary>
        /// 查找指定的资源文件。
        /// </summary>
        private void btnSearchResource_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            SearchCustomResources();
        }

        /// <summary>
        /// 在工作区管理器中选定上一个找到的资源条目。
        /// </summary>
        private void btnSearchPreview_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            SelectPreviewSearchedResource();
            e.Handled = true;//避免失焦
        }

        /// <summary>
        /// 在工作区管理器中选定上一个找到的资源条目。
        /// </summary>
        private void SelectPreviewSearchedResource()
        {
            if (searchResourceIndex > 0)
            {
                searchResourceIndex -= 1;
                if (searchResourceIndex >= 0 && searchResourceIndex < searchedResourceTreeviewItemList.Count)
                {
                    searchedResourceTreeviewItemList[searchResourceIndex].IsSelected = true;
                    ExpandWorkspaceTreeviewItem(searchedResourceTreeviewItemList[searchResourceIndex]);
                    tvWorkDirectory.Focus();
                }
            }
        }

        /// <summary>
        /// 在工作区管理器中选定下一个找到的资源条目。
        /// </summary>
        private void btnSearchNext_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            SelectNextSearchedResource();
            e.Handled = true;//避免失焦
        }

        /// <summary>
        /// 在工作区管理器中选定下一个找到的资源条目。
        /// </summary>
        private void SelectNextSearchedResource()
        {
            if (searchResourceIndex < searchedResourceTreeviewItemList.Count - 1)
            {
                searchResourceIndex += 1;
                if (searchResourceIndex >= 0 && searchResourceIndex < searchedResourceTreeviewItemList.Count)
                {
                    searchedResourceTreeviewItemList[searchResourceIndex].IsSelected = true;
                    ExpandWorkspaceTreeviewItem(searchedResourceTreeviewItemList[searchResourceIndex]);
                    tvWorkDirectory.Focus();
                }
            }
        }

        /// <summary>
        /// 展开指定的工作区管理器中的某个条目。
        /// </summary>
        /// <param name="item">要展开的某个条目，其所有上级都会被展开。</param>
        private void ExpandWorkspaceTreeviewItem(WorkspaceTreeViewItem item)
        {
            if (item == null) return;

            item.IsExpanded = true;

            var parent = item.ParentWorkspaceTreeViewItem;
            while (parent != null)
            {
                parent.IsExpanded = true;
                parent = parent.ParentWorkspaceTreeViewItem;
            }
        }
        #endregion

        /// <summary>
        /// 在查找框中按回车键执行查找操作。
        /// </summary>
        private void cmbFindText_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                if (cmbSearchArea.SelectedIndex == 0)
                {
                    btnFind_Click(sender, e);
                }
                else
                {
                    FindText(tvFindAndReplace);
                }
            }
        }

        /// <summary>
        /// 在替换框中按回车键执行替换操作。
        /// </summary>
        private void cmbReplaceTextInputBox_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                if (cmbSearchArea.SelectedIndex == 0)
                {
                    if ((Keyboard.GetKeyStates(Key.LeftShift) & KeyStates.Down) > 0 ||
                        (Keyboard.GetKeyStates(Key.RightShift) & KeyStates.Down) > 0)
                    {
                        btnReplaceAll_Click(sender, e);
                    }
                    else
                    {
                        btnReplace_Click(sender, e);
                    }
                    e.Handled = true;
                }
            }
        }

        /// <summary>
        /// 根据“查找范围”框中定义的查找范围来查找“查找”框中指定的文本。
        /// </summary>
        /// <param name="treeView">指定将查找到的结果显示在哪个树型框中。</param>
        public void FindText(TreeView treeView)
        {
            treeView.Items.Clear();
            FindText(cmbFindText.Text, (cmbSearchArea.SelectedItem as ComboBoxItem).Tag.ToString(), false, treeView);

            if (treeView.Items.Count <= 0)
            {
                treeView.Items.Add(new TreeViewItem() { Header = "<没找到结果...>", });
            }

            cmbFindText.Items.Add(cmbFindText.Text);

            if (rightToolBar.SelectedIndex != 1)
            {
                rightToolBar.SelectedIndex = 1;
            }

            if (cdRightToolsArea.ActualWidth < 140)
            {
                cdLeftToolsArea.Width = new GridLength(2, GridUnitType.Star);
            }
        }

        /// <summary>
        /// 在磁盘文档或当前正在编辑的文档之间查找指定文本内容。
        /// </summary>
        /// <param name="destText">如果指定了此参数，则按此参数查找。</param>
        /// <param name="preChar">前导字符，决定找到指定的文本后，双击后要选定的文本区域要向前移动到哪个字符。</param>
        /// <param name="nextChar">后缀字符，决定找到指定的文本后，双击后要选定的文本区域要向后移动到哪个字符。</param>
        private void FindText(string keyWord, string searchArea, bool forceRegex, TreeView treeView)
        {
            if (string.IsNullOrEmpty(keyWord)) return;
            try
            {
                if (treeView == null) treeView = tvFindAndReplace;

                treeView.Items.Clear();

                int keywordLength = keyWord.Length;

                var type = FindLineTreeViewItem.ItemType.Normal;
                if (keyWord == "](@" || keyWord == @"\[.*\]\(@.*\)")
                {
                    type = FindLineTreeViewItem.ItemType.Anchor;
                }

                switch (searchArea)
                {
                    case "ActiveDocument":
                        {
                            FindTextInActiveDocument(keyWord, forceRegex, treeView, type);
                            break;
                        }
                    case "OpenedDocuments":
                        {
                            FindTextInOpenedDocuments(keyWord, forceRegex, treeView, type);
                            break;
                        }
                    case "AllFiles":
                        {
                            //这个需要用到递归
                            FindTextInAllFiles(Globals.PathOfWorkspace, keyWord, forceRegex, treeView);
                            break;
                        }
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 在打开的所有文档中查找文本。
        /// </summary>
        /// <param name="keyWord">要查找的关键词。</param>
        /// <param name="forceRegex">是否强制使用正则表达式来查找。</param>
        /// <param name="treeView">将查找的结果放在哪个树型框中呈现。</param>
        /// <param name="type">指定要查找什么类型的文本，树型框的条目会呈现为不同的姿态。</param>
        private void FindTextInOpenedDocuments(string keyWord, bool forceRegex, TreeView treeView, FindLineTreeViewItem.ItemType type)
        {
            foreach (var item in this.mainTabControl.Items)
            {
                var efi = item as MarkdownEditor;
                if (efi != null)
                {
                    FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(efi.FullFilePath, efi.ShortFileName);
                    Regex regex = GetRegEx(keyWord, false, forceRegex);
                    var matches = regex.Matches(efi.EditorBase.Text);

                    foreach (Match match in matches)
                    {
                        if (match.Success)
                        {
                            var line = efi.EditorBase.Document.GetLineByOffset(match.Index);
                            var lineText = efi.EditorBase.Document.GetText(line.Offset, line.Length);
                            fdi.Items.Add(new FindLineTreeViewItem(efi.FullFilePath, efi.ShortFileName, line.LineNumber,
                                match.Index - line.Offset, match.Length, lineText, null, type));
                        }
                    }

                    if (fdi.Items.Count > 0)
                    {
                        treeView.Items.Add(fdi);
                        fdi.IsExpanded = true;
                        (fdi.Items[0] as TreeViewItem).IsSelected = true;
                    }
                }
            }
        }

        /// <summary>
        /// 在当前活动编辑器中查找文本。
        /// </summary>
        /// <param name="keyWord">要查找的关键词。</param>
        /// <param name="forceRegex">是否强制使用正则表达式来查找。</param>
        /// <param name="treeView">将查找的结果放在哪个树型框中呈现。</param>
        /// <param name="type">指定要查找什么类型的文本，树型框的条目会呈现为不同的姿态。</param>
        private void FindTextInActiveDocument(string keyWord, bool forceRegex, TreeView treeView, FindLineTreeViewItem.ItemType type)
        {
            var efi = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (efi != null)
            {
                FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(efi.FullFilePath, efi.ShortFileName);

                Regex regex = GetRegEx(keyWord, false, forceRegex);
                var matches = regex.Matches(efi.EditorBase.Text);

                foreach (Match match in matches)
                {
                    if (match.Success)
                    {
                        var line = efi.EditorBase.Document.GetLineByOffset(match.Index);
                        var lineText = efi.EditorBase.Document.GetText(line.Offset, line.Length);
                        fdi.Items.Add(new FindLineTreeViewItem(efi.FullFilePath, efi.ShortFileName, line.LineNumber,
                            match.Index - line.Offset, match.Length, lineText, null, type));
                    }
                }

                if (fdi.Items.Count > 0)
                {
                    treeView.Items.Add(fdi);
                    fdi.IsExpanded = true;
                    (fdi.Items[0] as TreeViewItem).IsSelected = true;
                }
            }
        }

        /// <summary>
        /// [递归方法]在指定目录下的所有 Markdown 文件中查找指定文本。
        /// </summary>
        /// <param name="directoryPath">基准目录，从该目录开始查找。</param>
        /// <param name="keyWord">查找关键词。</param>
        /// <param name="forceRegex">是否强制使用正则表达式方式查找。</param>
        /// <param name="treeView">将查找结果显示在哪个树型框中。</param>
        private void FindTextInAllFiles(string directoryPath, string keyWord, bool forceRegex, TreeView treeView)
        {
            if (Directory.Exists(directoryPath) == false) return;
            if (string.IsNullOrEmpty(keyWord)) return;
            try
            {
                var keywordLength = keyWord.Length;

                var type = FindLineTreeViewItem.ItemType.Normal;
                if (keyWord == "](@")
                {
                    type = FindLineTreeViewItem.ItemType.Anchor;
                }

                var directory = new DirectoryInfo(directoryPath);

                //先处理当前目录下的文件
                var childFilesInfos = directory.GetFiles();

                foreach (var childFileInfo in childFilesInfos)
                {
                    if (childFileInfo.FullName.ToLower().EndsWith(".md") == false) continue;

                    FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(childFileInfo.FullName, childFileInfo.Name);

                    //已打开的文件，按打开的情况算，没打开的，按磁盘文本查找。
                    var fileEditor = GetOpenedEditor(childFileInfo.FullName);
                    if (fileEditor != null)
                    {
                        Regex regex = GetRegEx(keyWord, false, forceRegex);
                        var matches = regex.Matches(fileEditor.EditorBase.Text);

                        foreach (Match match in matches)
                        {
                            if (match.Success)
                            {
                                var line = fileEditor.EditorBase.Document.GetLineByOffset(match.Index);
                                var lineText = fileEditor.EditorBase.Document.GetText(line.Offset, line.Length);
                                fdi.Items.Add(new FindLineTreeViewItem(fileEditor.FullFilePath, fileEditor.ShortFileName, line.LineNumber,
                                    match.Index - line.Offset, match.Length, lineText, null, type));
                            }
                        }
                    }
                    else
                    {
                        Regex regex = GetRegEx(keyWord, false, forceRegex);
                        var fileContent = File.ReadAllText(childFileInfo.FullName);
                        var matches = regex.Matches(fileContent);

                        foreach (Match match in matches)
                        {
                            if (match.Success)
                            {
                                var preLineEndMarkEndIndex = Math.Max(fileContent.LastIndexOf("\r", match.Index), fileContent.LastIndexOf("\n", match.Index));

                                var matchEndIndex = match.Index + match.Length;
                                var nextLineEndStartIndex = Math.Min(fileContent.IndexOf("\r", matchEndIndex), fileContent.IndexOf("\n", matchEndIndex));
                                var lineNumber = 1;
                                for (int i = 0; i < match.Index; i++)
                                {
                                    if (fileContent[i] == '\n') { lineNumber++; }
                                }

                                string lineText;
                                if (nextLineEndStartIndex - preLineEndMarkEndIndex > 0)
                                {
                                    if (preLineEndMarkEndIndex < 0)
                                    {
                                        if (nextLineEndStartIndex < 0)
                                        {
                                            lineText = fileContent;
                                        }
                                        else
                                        {
                                            lineText = fileContent.Substring(0, nextLineEndStartIndex);
                                        }
                                    }
                                    else
                                    {
                                        lineText = fileContent.Substring(preLineEndMarkEndIndex + 1, nextLineEndStartIndex - preLineEndMarkEndIndex - 1);
                                    }
                                }
                                else
                                {
                                    lineText = match.Value;
                                }

                                fdi.Items.Add(new FindLineTreeViewItem(childFileInfo.FullName, childFileInfo.Name, lineNumber,
                                   Math.Max(0, match.Index - preLineEndMarkEndIndex - 1), match.Length, lineText, null, type));
                            }
                        }
                    }

                    if (fdi.Items.Count > 0)
                    {
                        treeView.Items.Add(fdi);
                        fdi.IsExpanded = true;
                    }
                }

                //再递归处理子目录下的文件
                var childDirectories = directory.GetDirectories();
                foreach (var childDirectory in childDirectories)
                {
                    FindTextInAllFiles(childDirectory.FullName, keyWord, forceRegex, treeView);
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 根据“查找范围”框中定义的查找范围来查找“查找”框中指定的文本。
        /// </summary>
        private void btnFindText_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            FindText(tvFindAndReplace);
        }

        /// <summary>
        /// 查找资源按钮的变色效果。
        /// </summary>
        private void vbSearchResource_MouseEnter(object sender, MouseEventArgs e)
        {
            (sender as Border).Background = Brushes.LightBlue;
        }

        /// <summary>
        /// 查找资源按钮的变色效果。
        /// </summary>
        private void vbSearchResource_MouseLeave(object sender, MouseEventArgs e)
        {
            (sender as Border).Background = Brushes.Transparent;
        }

        /// <summary>
        /// 查找上一个资源按钮的变色效果。
        /// </summary>
        private void vbPreviewFindedResource_MouseEnter(object sender, MouseEventArgs e)
        {
            (sender as Border).Background = Brushes.LightBlue;
        }

        /// <summary>
        /// 查找上一个资源按钮的变色效果。
        /// </summary>
        private void vbPreviewFindedResource_MouseLeave(object sender, MouseEventArgs e)
        {
            (sender as Border).Background = Brushes.Transparent;
        }

        /// <summary>
        /// 查找下一个资源按钮的变色效果。
        /// </summary>
        private void vbNextFindedResource_MouseEnter(object sender, MouseEventArgs e)
        {
            (sender as Border).Background = Brushes.LightBlue;
        }

        /// <summary>
        /// 查找下一个资源按钮的变色效果。
        /// </summary>
        private void vbNextFindedResource_MouseLeave(object sender, MouseEventArgs e)
        {
            (sender as Border).Background = Brushes.Transparent;
        }

        /// <summary>
        /// 折叠当前活动编辑器中选择区所在的折叠块。
        /// </summary>
        private void miFoldThisBlock_Click(object sender, RoutedEventArgs e)
        {
            var efi = mainTabControl.SelectedItem as MarkdownEditor;
            if (efi == null) return;

            efi.EditorBase.FoldSelectedBlock();
        }

        /// <summary>
        /// 折叠当前活动编辑器中所有可折叠区域。
        /// </summary>
        private void miFoldingAll_Click(object sender, RoutedEventArgs e)
        {
            var efi = mainTabControl.SelectedItem as MarkdownEditor;
            if (efi == null) return;

            foreach (var i in efi.EditorBase.FoldingManager.AllFoldings)
            {
                i.IsFolded = true;
            }
        }

        /// <summary>
        /// 展开当前活动编辑器中所有的折叠区域。
        /// </summary>
        private void miUnFoldingAll_Click(object sender, RoutedEventArgs e)
        {
            var efi = mainTabControl.SelectedItem as MarkdownEditor;
            if (efi == null) return;

            foreach (var i in efi.EditorBase.FoldingManager.AllFoldings)
            {
                i.IsFolded = false;
            }
        }

        /// <summary>
        /// 向指定目录导出当前工作区下所有 Html 文件及其资源文件。
        /// </summary>
        private void miOutportWebSite_Click(object sender, RoutedEventArgs e)
        {
            string destOutportFolder;

            //选择新工作目录
            System.Windows.Forms.FolderBrowserDialog fbd = new System.Windows.Forms.FolderBrowserDialog()
            {
                Description = "　　请选择一个文件夹（目录）作为导出目标。\r\n　　导出时会简单地将除Markdown文件以外的所有文件复制到目标文件夹中。",
                ShowNewFolderButton = true,
            };
            System.Windows.Interop.HwndSource source = PresentationSource.FromVisual(this) as System.Windows.Interop.HwndSource;

            System.Windows.Forms.IWin32Window win = new WinFormWindow(source.Handle);
            System.Windows.Forms.DialogResult result = fbd.ShowDialog(win);
            if (result.Equals(System.Windows.Forms.DialogResult.OK))
            {
                destOutportFolder = fbd.SelectedPath;
            }
            else return;

            OutportFiles(destOutportFolder);
        }

        /// <summary>
        /// 向指定目录导出当前工作区下所有 Html 文件及其资源文件。
        /// </summary>
        /// <param name="destOutportFolder">指定的导出目标目录的绝对路径。</param>
        private void OutportFiles(string destOutportFolder)
        {
            if (Directory.Exists(destOutportFolder) == false)
            {
                LMessageBox.Show("　　指定目录不存在！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (destOutportFolder[destOutportFolder.Length - 1] != Path.DirectorySeparatorChar)
            {
                destOutportFolder += Path.DirectorySeparatorChar;
            }

            try
            {
                bool compileBeforeOutport = false;

                var result = LMessageBox.Show("　　导出前要重新编译所有Markdown文件吗？", Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (result == MessageBoxResult.Yes)
                {
                    compileBeforeOutport = true;
                }

                List<FileSystemEntry> fileSystemEntries = new List<FileSystemEntry>();

                CopyCompiledDirectory(Globals.PathOfWorkspace, destOutportFolder, compileBeforeOutport, ref fileSystemEntries);

                //生成一个html索引文档。
                var indexFilePath = BuildIndexOfCompiledHtmlFiles(fileSystemEntries);
                if (File.Exists(destOutportFolder + "_Index.html") == false)
                {
                    File.Copy(indexFilePath, destOutportFolder + "_Index.html");
                }

                // var lines = File.ReadAllLines(Globals.PathOfHistoryWorkspaceFileFullName);
                if (File.Exists(Globals.PathOfHistoryOutputFileFullName))
                {
                    string[] lines = File.ReadAllLines(Globals.PathOfHistoryOutputFileFullName, new UnicodeEncoding());
                    var newLinesList = new List<string>();
                    foreach (var line in lines)
                    {
                        if (line == destOutportFolder) continue;

                        newLinesList.Add(line);
                    }

                    var newLines = new string[newLinesList.Count + 1];
                    newLines[0] = destOutportFolder;
                    for (int i = 1; i < newLines.Length; i++)
                    {
                        newLines[i] = newLinesList[i - 1];
                    }

                    File.WriteAllLines(Globals.PathOfHistoryOutputFileFullName, newLines, new UnicodeEncoding());
                }
                else
                {
                    File.WriteAllLines(Globals.PathOfHistoryOutputFileFullName, new string[] { destOutportFolder }, new UnicodeEncoding());
                }

                for (int i = lbxHistoryOutport.Items.Count - 1; i >= 0; i--)
                {
                    var rwitem = lbxHistoryOutport.Items[i] as RecentDirectoryListBoxItem;
                    if (rwitem != null && rwitem.DirectoryPath == destOutportFolder)
                    {
                        lbxHistoryOutport.Items.Remove(rwitem);
                    }
                }

                var row = new RecentDirectoryListBoxItem(destOutportFolder)
                {
                    ToolTip = "双击将编译的所有 Html 文件及资源文件导出到该目录",
                };
                row.MouseDoubleClick += row_MouseDoubleClick;

                lbxHistoryOutport.Items.Insert(0, row);
                lbxHistoryOutport.SelectedIndex = 0;

                tabControlLeftTools.SelectedIndex = 0;
                RefreshOutputHistoryList();

                //LMessageBox.Show("　　已将工作区目录下所有非Markdown文件（目录）导出到以下磁盘位置：\r\n　　" +
                //    destOutportFolder, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);

                System.Diagnostics.Process.Start("explorer.exe", $"\"{destOutportFolder}\"");
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
            }
        }

        /// <summary>
        /// 刷新“历史导出列表”。
        /// </summary>
        private void RefreshOutputHistoryList()
        {
            for (int i = 0; i < lbxHistoryOutport.Items.Count; i++)
            {
                var item = lbxHistoryOutport.Items[i] as RecentDirectoryListBoxItem;
                if (item == null) continue;

                item.IndexText = (i + 1).ToString();
            }
        }

        /// <summary>
        /// 为编译的所有 Html 创建一个带目录结构的 Html 索引文件。
        /// </summary>
        /// <param name="fileSystemEntries">编译过的所有 Html 文件的路径信息。</param>
        /// <returns>返回 Html 索引文件的内容文本。</returns>
        private string BuildIndexOfCompiledHtmlFiles(List<FileSystemEntry> fileSystemEntries)
        {
            if (fileSystemEntries.Count > 0)
            {
                var charSetText = "UTF-8";
                var encoding = Encoding.UTF8;
                if (miGb2312.IsChecked)
                {
                    charSetText = "GB2312";
                    encoding = Encoding.GetEncoding("gb2312");
                }
                StringBuilder sbIndex = new StringBuilder();

                sbIndex.Append("<!DOCTYPE html>\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n"
                    + "<meta name=\"viewport\" content=\"width = device - width, user - scalable = yes\" />\n"
                    + $"<meta http-equiv=\"Content - Type\" content=\"text / html; charset = {charSetText}\">\n"
                    + $"<title>{Globals.WorkspaceShortName}</title>\n<link rel=\"stylesheet\" href=\"./lesson_"
                    + Globals.MainWindow.ThemeText + ".css\" type=\"text/css\"></head>\n<body>"
                    + $"<p class='fileheader' id='file_header'>{Globals.WorkspaceShortName}</p><hr />"
                    + "\n");

                int tmpIndex = Globals.PathOfWorkspace.Length;
                string preDirectory = "/";

                foreach (FileSystemEntry fileSystemEntry in fileSystemEntries)
                {
                    string prefix;
                    if (fileSystemEntry.FileFullName.StartsWith("directory_entry:///"))
                    {
                        //目录
                        preDirectory = fileSystemEntry.FileFullName.Substring(19).Substring(Globals.PathOfWorkspace.Length).Replace("\\", "/");
                        if (preDirectory.EndsWith("/") == false) preDirectory += "/";

                        var pieces = preDirectory.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
                        var sb = new StringBuilder();
                        foreach (var p in pieces)
                        {
                            sb.Append(" >> ");
                            sb.Append(FormatDocumentTitle(p));
                        }
                        var preDirectoryText = sb.ToString();
                        prefix = BuildPrefix(preDirectory, true);

                        DirectoryInfo di = new DirectoryInfo(fileSystemEntry.FileFullName.Substring(19));
                        var metaFilePath = (di.FullName.EndsWith("\\") ? di.FullName : (di.FullName + "\\")) + "_" + di.Name + ".html";
                        if (File.Exists(metaFilePath))
                        {
                            FileInfo metaFileInfo = new FileInfo(metaFilePath);

                            string tmpPath2 = di.FullName;
                            if (di.FullName.StartsWith(Globals.PathOfWorkspace))
                            {
                                tmpPath2 = /*destPath + */ di.FullName.Substring(tmpIndex).Replace("\\", "/").Substring(preDirectory.Length - 1);
                                if (tmpPath2.StartsWith("/")) tmpPath2 = tmpPath2.Substring(1);
                            }
                            sbIndex.Append("<p>" + prefix + "<a href=\"./" +
                                (preDirectory == "/" ? "" : preDirectory) + tmpPath2 + metaFileInfo.Name + "\">" + " >> " + GetHtmlFileTile(metaFileInfo) + "</a></p>\n");
                        }
                        else
                        {
                            sbIndex.Append("<p>" + prefix + " >> " + FormatDocumentTitle(di) + " </p>");

                        }
                        continue;
                    }

                    //html文件
                    var fileInfo = new FileInfo(fileSystemEntry.FileFullName);

                    if (fileInfo.Name.StartsWith("_") == false && fileInfo.Name.ToLower().EndsWith(".html"))
                    {
                        var fullDirectoryPath = fileInfo.DirectoryName;
                        if (fullDirectoryPath.EndsWith("\\") == false)
                        {
                            fullDirectoryPath += "\\";
                        }

                        if (fullDirectoryPath.StartsWith(Globals.PathOfWorkspace))
                        {
                            preDirectory = fullDirectoryPath.Substring(Globals.PathOfWorkspace.Length).Replace("\\", "/");
                        }
                        else
                        {
                            preDirectory = "";
                        }

                        prefix = BuildPrefix(preDirectory, false);

                        //元文件不在这里添加条目。                       
                        sbIndex.Append("<p>" + prefix + "<a href=\"./" +
                            (preDirectory == "/" ? "" : preDirectory) + fileInfo.Name + "\">" + GetHtmlFileTile(fileInfo) + "</a></p>\n");
                    }
                }

                sbIndex.Append($"<hr /><div class='foot'><p id='compile_time'>{DateTime.Now.ToString()}</p></div></body>\n</html>");

                var indexHtmlFilePath = Globals.PathOfWorkspace + "_index.html";

                File.WriteAllText(indexHtmlFilePath, sbIndex.ToString(), encoding);
                return indexHtmlFilePath;
            }
            else return "";
        }

        /// <summary>
        /// 将字符串开头的数字去除。例如：1－2－3－xxx，格式化后只保留xxx。但如果只剩下空格，就返回原始值。
        /// </summary>
        private string FormatDocumentTitle(DirectoryInfo di)
        {
            return FormatDocumentTitle(di.Name);
        }

        /// <summary>
        /// 取指定 Html 文档中在“<Title></Title>”之中的标题文本。
        /// </summary>
        /// <param name="fileInfo">Html 文件的位置。</param>
        /// <returns>返回 Html 文件的标题。</returns>
        private string GetHtmlFileTile(FileInfo fileInfo)
        {
            if (fileInfo == null || fileInfo.Exists == false) return "";

            //< title > 2016年江苏省普通高等学校招生全国统一考试（江苏卷）（选修科目）说明 </ title >

            Regex regex = new Regex(@"(?<=(<title>)).*(?=(</title>))");

            var encoding = (miUtf8.IsChecked == true) ? Encoding.UTF8 : Encoding.GetEncoding("GB2312");

            using (StreamReader sr = new StreamReader(fileInfo.FullName, encoding))
            {
                var lineText = sr.ReadLine();
                while (lineText != null)
                {
                    var match = regex.Match(lineText);
                    if (match.Success)
                    {
                        return match.Value;
                    }

                    lineText = sr.ReadLine();
                }
            }

            return "";
        }

        /// <summary>
        /// 创建 Html 目录文件时，需要知道各目录之间的层级关系，此关系由前缀决定。
        /// </summary>
        /// <param name="preDirectory">前一目录路径。</param>
        /// <param name="isfolder">是否目录。</param>
        /// <returns>返回前缀字符串，表示层级关系。</returns>
        private string BuildPrefix(string preDirectory, bool isfolder)
        {
            if (string.IsNullOrEmpty(preDirectory)) return string.Empty;
            if (preDirectory == "/" || preDirectory == "\\") return string.Empty;

            StringBuilder sb = new StringBuilder();
            foreach (char c in preDirectory)
            {
                if (c == '/' || c == '\\') sb.Append("　　");
            }

            if (isfolder)
            {
                var result = sb.ToString();
                if (preDirectory.EndsWith("/") || preDirectory.EndsWith("\\"))
                {
                    if (result.EndsWith("　　")) return result.Substring(0, result.Length - 2);
                    else return result;
                }
                else return result;
            }
            else return sb.ToString();
        }

        /// <summary>
        /// 双击“历史导出目录”列表中某一项，将当前工作区向该项指向的目录导出编译的所有 Html 文件以及资源文件。
        /// </summary>
        void row_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            OutportFiles((sender as RecentDirectoryListBoxItem).DirectoryPath);
        }

        /// <summary>
        /// 编译所有指定的 Markdown 文件。
        /// </summary>
        /// <param name="compiledFiles">用于返回被编译的 Markdown 文件的信息，这样可用于生成目录 Html 文件。</param>
        public static void CompileAllMdFiles(ref List<FileSystemEntry> compiledFiles)
        {
            CopyCompiledDirectory(Globals.PathOfWorkspace, "", true, ref compiledFiles);
        }

        /// <summary>
        /// 编译指定目录下（含下级）所有 Markdown 文件，并将编译好的 Html 文件按目录结构输出到指定的另一个目录中。
        /// </summary>
        /// <param name="srcDirectory">Markdown 文件所在的源目录的路径。</param>
        /// <param name="destDirectory">传入无效的路径可以避免复制，这样就只是将 Markdown 文件编译为 Html 文件了。编译的 Html 文件与 Markdown 文件在同一目录下。</param>
        /// <param name="compileMarkdownFilesBeforeCopy">在向指定目录复制编译生成的 Html 文件之前是否先重新编译一下。</param>
        /// <param name="fileSystemEntries">此参数用于生成目录索引 Html 文件。</param>
        public static void CopyCompiledDirectory(string srcDirectory, string destDirectory, bool compileMarkdownFilesBeforeCopy, ref List<FileSystemEntry> fileSystemEntries)
        {
            String[] Entries;

            if (string.IsNullOrEmpty(destDirectory) == false && destDirectory[destDirectory.Length - 1] != Path.DirectorySeparatorChar)
            {
                destDirectory += Path.DirectorySeparatorChar;
            }

            //不能用下面这段代码，因为destDirectory完全可能不是合法的目录路径，这样会引发异常。
            //if (string.IsNullOrEmpty(destDirectory) == false && !Directory.Exists(destDirectory))
            //{
            //    try
            //    {
            //        Directory.CreateDirectory(destDirectory);
            //    }
            //    catch (Exception ex)
            //    {
            //        LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
            //        return;
            //    }
            //}

            Entries = Directory.GetFileSystemEntries(srcDirectory);

            if (compileMarkdownFilesBeforeCopy)
            {
                foreach (string fileEntry in Entries)
                {
                    //必须分两次循环，先考虑编译，再复制
                    if (File.Exists(fileEntry))
                    {
                        var srcFilePath = fileEntry;// srcDirectory + Path.GetFileName(fileEntry);
                        if (srcFilePath.ToLower().EndsWith(".md"))
                        {
                            string htmlTitle = "";

                            if (Globals.MainWindow.IgnoreEncryptedFiles)
                            {
                                string password, passwordTip;
                                var isEncrypted = CustomMarkdownSupport.IsFileEncrypted(srcFilePath, out password, out passwordTip);
                                if (isEncrypted) continue;//忽略被加密的文档。
                            }

                            var filePath = CustomMarkdownSupport.Compile(srcFilePath,
                                        Globals.MainWindow.HtmlHeadersCollapse, ref htmlTitle);
                            if (filePath == null) continue;
                        }
                    }
                }
            }

            foreach (string entry in Entries)
            {
                if (Directory.Exists(entry))
                {
                    if (entry.EndsWith("~") == false && entry.EndsWith("~" + Path.DirectorySeparatorChar) == false)
                    {
                        fileSystemEntries.Add(new FileSystemEntry()
                        {
                            FileFullName = "directory_entry:///" + entry,
                            Title = "",
                        });
                    }

                    if (string.IsNullOrWhiteSpace(destDirectory))
                    {
                        CopyCompiledDirectory(entry, "", compileMarkdownFilesBeforeCopy, ref fileSystemEntries);
                    }
                    else
                    {
                        CopyCompiledDirectory(entry, destDirectory + Path.GetFileName(entry), compileMarkdownFilesBeforeCopy, ref fileSystemEntries);
                    }
                }
                else
                {
                    if (entry.ToLower().EndsWith(".html") && File.Exists(entry) &&
                        entry.ToLower().EndsWith("~.html") == false)
                    {
                        var fileInfo = new FileInfo(entry);
                        if (fileInfo.Name.StartsWith("_") == false)
                        {
                            fileSystemEntries.Add(
                                new FileSystemEntry()
                                {
                                    FileFullName = entry,
                                    Title = fileInfo.Name,
                                });
                        }
                    }

                    var destFileFullPath = destDirectory + Path.GetFileName(entry);
                    if (string.IsNullOrWhiteSpace(destDirectory) == false)//为空字符串时不复制。
                    {
                        if (Directory.Exists(destDirectory) == false && string.IsNullOrWhiteSpace(destDirectory) == false)
                        {
                            try
                            {
                                Directory.CreateDirectory(destDirectory);

                                if (destFileFullPath.ToLower().EndsWith(".md") == false && File.Exists(destFileFullPath) == false)
                                {
                                    System.IO.File.Copy(entry, destFileFullPath, true);
                                }
                            }
                            catch (Exception ex)
                            {
                                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
                            }
                        }
                        else
                        {

                            if (destFileFullPath.ToLower().EndsWith(".md") == false && File.Exists(destFileFullPath) == false)
                            {
                                System.IO.File.Copy(entry, destFileFullPath, true);
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// 编译当前活动编辑器，并预览之。
        /// </summary>
        private void btnCompileAndPreviewHtml_MouseLeftButtonDown(object sender, RoutedEventArgs e)
        {
            CompileAndPreviewHtml(true);
        }

        /// <summary>
        /// 插入 Html 格式的水平线标记文本。
        /// </summary>
        private void miInsertHorizontalLine_Click(object sender, RoutedEventArgs e)
        {
            MenuItem mi = sender as MenuItem;
            if (mi == null) return;

            var value = int.Parse(mi.Tag as string);

            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            eti.EditorBase.SelectedText = "<hr class=\"hr" + value + "\"/>\r\n";
            var destSel = eti.EditorBase.SelectionStart + eti.EditorBase.SelectionLength;
            if (destSel < eti.EditorBase.Document.TextLength)
            {
                eti.EditorBase.Select(destSel, 0);
            }
        }

        /// <summary>
        /// 用一对方括弧将选定文本片段括起来。形如：【原文本】。
        /// </summary>
        private void miWrapWithSquareQuoters_Click(object sender, RoutedEventArgs e)
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;
            eti.EditorBase.WrapTextWithSquareQuotes();
        }

        /// <summary>
        /// 插入一个选择题相关标记。
        /// </summary>
        private void miInsertChoiceQuestion_Click(object sender, RoutedEventArgs e)
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;
            eti.EditorBase.InsertChoiceQuestionTags();
        }

        /// <summary>
        /// 插入一个判断题相关标记。
        /// </summary>
        private void miInsertJudgeQuestion_Click(object sender, RoutedEventArgs e)
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;
            eti.EditorBase.InsertJudgeQuestionTags((sender as MenuItem).Tag.ToString());
        }

        /// <summary>
        /// 检验当前活动编辑器中的试题是否合法（不合法的是指格式不全，不能用于演示。）
        /// </summary>
        private void miValidateTestPaper_Click(object sender, RoutedEventArgs e)
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            StringBuilder errorMsg = new StringBuilder();
            if (QuestionValidateManager.ValidateAsTestPaper(OutLineExamsText(eti.EditorBase.Document.Text), errorMsg))
            {
                tbxValidatedInfo.Text = "文件通过合法性验证，可以使用。\r\n==========√√√√√==========\r\n";
            }
            else
            {
                tbxValidatedInfo.Text = "文件未通过合法性验证，错误消息如下：\r\n============××××××============\r\n" + errorMsg.ToString();
            }

            if (rightToolBar.SelectedItem != tabValidateDocument)
            {
                rightToolBar.SelectedItem = tabValidateDocument;
            }
        }

        /// <summary>
        /// 演示当前活动编辑器中所有试题。
        /// </summary>
        private void miPresentation_Click(object sender, RoutedEventArgs e)
        {
            PresentationExams();
        }

        /// <summary>
        /// 抽取所有与试题相关的文本行。
        /// </summary>
        /// <param name="src">源文本。</param>
        /// <returns>返回源文本中与试题相关的所有文本行。</returns>
        private string OutLineExamsText(string src)
        {
            if (string.IsNullOrEmpty(src)) return "";

            var lines = src.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
            StringBuilder sb = new StringBuilder();
            foreach (var l in lines)
            {
                if (Question.IsExamTextLine(l))
                {
                    sb.Append(l);
                    sb.Append("\r\n");
                }
            }

            return sb.ToString();
        }

        /// <summary>
        /// 演示当前活动编辑器中的试题（如果有）。
        /// </summary>
        public void PresentationExams()
        {
            var edit = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (edit == null)
            {
                LMessageBox.Show("没有文档可以显示！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var examsText = OutLineExamsText(edit.EditorBase.Document.Text);

            StringBuilder errorMsg = new StringBuilder();
            if (QuestionValidateManager.ValidateForPresentation(examsText, errorMsg))//这包括了填空题。
            {
                tbxValidatedInfo.Text = "文件通过合法性验证，可以使用。\r\n========√√√√========\r\n";
            }
            else
            {
                tbxValidatedInfo.Text = "文件未通过合法性验证，错误消息如下：\r\n=========××××========\r\n" + errorMsg.ToString();

                if (rightToolBar.SelectedItem != tabValidateDocument)
                {
                    rightToolBar.SelectedItem = tabValidateDocument;
                }

                LMessageBox.Show("　　此文档中无试题或试题有格式错误，无法演示！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);

                return;
            }

            //这句不能少，否则头一次显示不出任何试题。
            edit.PresentationWindow.BuildQuestions(examsText);
            edit.PresentationWindow.ShowDialog();
        }

        /// <summary>
        /// Html 预览区 TabItem 标头双击编译当前活动编辑器并预览。
        /// </summary>
        private void TextBlock_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (e.ClickCount == 2)
            {
                CompileAndPreviewHtml(true);
            }
        }

        /// <summary>
        /// Html 预览区 TabItem 标头变色效果。
        /// </summary>
        private void StackPanel_MouseEnter(object sender, MouseEventArgs e)
        {
            (sender as StackPanel).Background = Brushes.LightGray;
        }

        /// <summary>
        /// Html 预览区 TabItem 标头变色效果。
        /// </summary>
        private void StackPanel_MouseLeave(object sender, MouseEventArgs e)
        {
            (sender as StackPanel).Background = Brushes.Transparent;
        }

        /// <summary>
        /// 创建试题演示界面所需要的提问名册。演示试题时，按特定快捷键就可以弹出一个学生名字提问。
        /// </summary>
        private void miCreateStudentsList_Click(object sender, RoutedEventArgs e)
        {
            var inputBoxResult = InputBox.Show(Globals.AppName, "　　请输入班级名：", "", false);

            if (string.IsNullOrEmpty(inputBoxResult)) return;

            try
            {
                string studentListDirectoryPath = Globals.PathOfUserFolder + (Globals.PathOfUserFolder.EndsWith("\\") ? "" : "\\") + "StudentsLists\\";
                if (Directory.Exists(studentListDirectoryPath) == false)
                {
                    Directory.CreateDirectory(studentListDirectoryPath);
                }

                DirectoryInfo studentListDirectoryinfo = new DirectoryInfo(studentListDirectoryPath);

                var newStudentsListFilePath = studentListDirectoryPath + (inputBoxResult.EndsWith(".txt") ? inputBoxResult : (inputBoxResult + ".txt"));
                if (File.Exists(newStudentsListFilePath) == false)
                {
                    //或不存在同名文件，就创建一个。
                    File.WriteAllText(newStudentsListFilePath, Properties.Resources.SampleClassFileContent);
                }

                //编辑文件
                var sw = new PlainTextEditor(newStudentsListFilePath)
                {
                    Owner = this,
                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
                    Title = Globals.AppName + " - 创建班级名册",
                };

                if (File.Exists(newStudentsListFilePath))
                {
                    sw.tbx.Text = File.ReadAllText(newStudentsListFilePath);
                }

                if (string.IsNullOrEmpty(sw.tbx.Text))
                {
                    sw.tbx.Text = Properties.Resources.SampleClassFileContent;
                }

                sw.ShowDialog();
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
            }
        }

        /// <summary>
        /// 切换“IsExamEnabled”开关。此开关打开时才能使用试题编辑、演示功能。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void miIsExamEnabled_Click(object sender, RoutedEventArgs e)
        {
            if (this.IsExamEnabled == false && this.IsAutoCompletionEnabled == false)
            {
                var result = LMessageBox.Show("需要打开【自动完成】，要继续吗？", Globals.AppName,
                     MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (result != MessageBoxResult.Yes) return;

                miIsAutoCompletionEnabled.IsChecked = true;
                this.IsAutoCompletionEnabled = true;
                this.configManager.Set("IsAutoCompletionEnabled", true.ToString());
            }

            miIsExamEnabled.IsChecked = !miIsExamEnabled.IsChecked;
            this.IsExamEnabled = miIsExamEnabled.IsChecked;
            this.configManager.Set("IsExamEnabled", miIsExamEnabled.IsChecked.ToString());
        }

        /// <summary>
        /// 切换“IsAutoCompletionEnabled”属性的开关。此开关打开时才能使用“自动完成”功能。
        /// </summary>
        private void miIsAutoCompletionEnabled_Clicked(object sender, RoutedEventArgs e)
        {
            miIsAutoCompletionEnabled.IsChecked = !miIsAutoCompletionEnabled.IsChecked;
            this.IsAutoCompletionEnabled = miIsAutoCompletionEnabled.IsChecked;
            this.configManager.Set("IsAutoCompletionEnabled", miIsAutoCompletionEnabled.IsChecked.ToString());

            if (this.IsAutoCompletionEnabled == false)
            {
                if (this.IsExamEnabled)
                {
                    this.miIsExamEnabled.IsChecked = this.IsExamEnabled = false;
                    this.configManager.Set("IsExamEnabled", false.ToString());
                }

                if (this.IsEnToChineseDictEnabled)
                {
                    this.miIsEnToChineseDictEnabled.IsChecked = this.IsEnToChineseDictEnabled = false;
                    this.configManager.Set("IsEnToChineseDictEnabled", false.ToString());
                }
            }
        }

        /// <summary>
        /// 双击左下角图像预览区，选定当前预览的图像文件在工作区管理器中的对应条目。
        /// </summary>
        private void imagePreviewOutBorder_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (e.ClickCount == 2)
            {
                var wti = (sender as Border).Tag as WorkspaceTreeViewItem;
                if (wti == null) return;

                wti.IsSelected = true;
            }
        }

        /// <summary>
        /// 根据“查找范围”框的限制，在指定范围的 Markdown 文件中查找所有的“锚”。
        /// </summary>
        private void btnFindAnchors_MouseLeftButtonDown(object sender, RoutedEventArgs e)
        {
            //锚的定义方式：
            //[锚名](@锚ID)
            //锚名可以省略，但锚ID不能省略。
            //编译后，会变成这样：
            //<a id="锚ID">锚名</a>
            string keyWord = @"\[.*\]\(@.*\)";

            FindText(keyWord, (cmbSearchArea.SelectedItem as ComboBoxItem).Tag.ToString(), true, tvFindAndReplace);

            if (rightToolBar.SelectedIndex != 1)
            {
                rightToolBar.SelectedIndex = 1;
            }

            if (cdRightToolsArea.ActualWidth < 140)
            {
                cdLeftToolsArea.Width = new GridLength(2, GridUnitType.Star);
            }
        }

        /// <summary>
        /// 返回当前活动的 Markdown 编辑器。
        /// </summary>
        public MarkdownEditor ActivedEditor
        {
            get
            {
                if (this.mainTabControl.Items.Count <= 0) return null;

                return this.mainTabControl.SelectedItem as MarkdownEditor;
            }
        }

        /// <summary>
        /// 指示是否启动时全屏。
        /// </summary>
        public bool StartFullScreen { get; private set; }

        /// <summary>
        /// 在当前文档中查找对工作区管理器中选定条目的引用。
        /// </summary>
        private void miFindRefInActiveDocument_Click(object sender, RoutedEventArgs e)
        {
            FindRef("ActiveDocument");
        }

        /// <summary>
        /// 根据“查找范围”查找所有对当前工作区管理器中选定条目的引用。
        /// </summary>
        /// <param name="findArea">指定查找的范围，包括：“ActiveDocument”、“OpenedDocuments”、“AllFiles”之一。</param>
        private void FindRef(string findArea)
        {
            var wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (wtvi == null) return;

            var shortName = wtvi.ShortName;
            if (shortName.ToLower().EndsWith(".md"))
            {
                shortName = shortName.Substring(0, shortName.Length - 3);
            }

            if (string.IsNullOrEmpty(shortName)) return;

            FindText(shortName, findArea, false, tvFindAndReplace);

            rightToolBar.SelectedItem = tiReplace;
        }

        /// <summary>
        /// 查找所有打开的文档中对当前条目的引用。
        /// </summary>
        private void miFindRefInOpenedDocument_Click(object sender, RoutedEventArgs e)
        {
            FindRef("OpenedDocuments");
        }

        /// <summary>
        /// 按工作区管理器中选定条目的名称查找工作区（及其下级目录）中所有 Markdown 文件中对此文件（或目录）的引用。
        /// 这通常用于改变目录（或文件）的名称前，以防止这些引用失效。
        /// 一般情况下，不应该更改目录（或文件）的名称，以防止引用失效。
        /// 如果有排序的需要，应该在创建时给文件（或目录）赋予数字开头的名称，且数字不应连贯。
        /// </summary>
        private void miFindRefInDiskFiles_Click(object sender, RoutedEventArgs e)
        {
            bool someFileIsModified = false;
            foreach (var i in this.mainTabControl.Items)
            {
                MarkdownEditor me = i as MarkdownEditor;
                if (me == null) continue;

                if (me.IsModified)
                {
                    someFileIsModified = true;
                    break;
                }
            }

            if (someFileIsModified)
            {
                var result = LMessageBox.Show("　　这个功能只管从磁盘上查找，而不管当前正在编辑的内容，所以查找的结果未必准确。\r\n" +
                    "　　如果要保证查找结果准确，请先保存这些文档或者使用“查找打开的文档”、“查找当前文档”这两个菜单条目！\r\n\r\n　　要继续吗？",
                    Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (result != MessageBoxResult.Yes) return;
            }

            FindRef("AllFiles");
        }

        /// <summary>
        /// 给当前活动编辑器中选中的文本添加倾斜标记（不能跨行）。
        /// </summary>
        private void miItalic_Click(object sender, RoutedEventArgs e)
        {
            WrapWithItalic();
        }

        /// <summary>
        /// 给当前活动编辑器中选中的文本添加加粗标记（不能跨行）。
        /// </summary>
        private void miBold_Click(object sender, RoutedEventArgs e)
        {
            WrapWithBold();
        }

        /// <summary>
        /// 给当前活动编辑器中选中的文本添加删除线标记（不能跨行）。
        /// </summary>
        private void miUnderLine_Click(object sender, RoutedEventArgs e)
        {
            WrapWithUTag();
        }

        /// <summary>
        /// 给当前活动编辑器中选中的文本添加下划线标记（不能跨行）。
        /// </summary>
        private void miStrikeLine_Click(object sender, RoutedEventArgs e)
        {
            WrapWithSTag();
        }

        /// <summary>
        /// 切换“启动时全屏（StartFullScreen）”选项的开关。开启时下一次启动后会立即全屏。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void miStartFullScreen_Click(object sender, RoutedEventArgs e)
        {
            miStartFullScreen.IsChecked = !miStartFullScreen.IsChecked;
            StartFullScreen = miStartFullScreen.IsChecked;
            configManager.Set("StartFullScreen", miStartFullScreen.IsChecked.ToString());
        }

        /// <summary>
        /// 切换全屏状态。
        /// </summary>
        private void miFullScreen_Click(object sender, RoutedEventArgs e)
        {
            SwitchFullScreen(mainMenu.Visibility == Visibility.Visible);

            if (mainMenu.Visibility == Visibility.Visible)
            {
                miFullScreen.IsChecked =
                miFullScreenC.IsChecked = false;
            }
            else
            {
                miFullScreen.IsChecked =
                miFullScreenC.IsChecked = true;
            }
        }

        /// <summary>
        /// 全屏预览。
        /// </summary>
        private void miPreviewFullScreen_Click(object sender, RoutedEventArgs e)
        {
            PreviewAsFullScreen();
        }

        /// <summary>
        /// 全屏。
        /// </summary>
        private void btnFullScreen_Click(object sender, RoutedEventArgs e)
        {
            SwitchFullScreen(true);
        }

        /// <summary>
        /// 旧版的自定义最小化窗口按钮。
        /// </summary>
        private void btnMinize_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            this.WindowState = WindowState.Minimized;
        }

        /// <summary>
        /// 旧版的自定义恢复窗口按钮。
        /// </summary>
        private void btnRestore_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (this.WindowState == WindowState.Normal)
            {
                this.WindowState = WindowState.Maximized;
            }
            else if (this.WindowState == WindowState.Maximized)
            {
                this.WindowState = WindowState.Normal;
            }
        }

        /// <summary>
        /// 旧版的自定义关闭窗口按钮。
        /// </summary>
        private void btnClose_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            this.Close();
        }

        /// <summary>
        /// 旧版的自定义窗口最大化、最小化、恢复图标。
        /// </summary>
        private BitmapImage restoreImageSource = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/restore.png"));
        /// <summary>
        /// 旧版的自定义窗口最大化、最小化、恢复图标。
        /// </summary>
        private BitmapImage maxsizeImageSource = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/maxsize.png"));

        /// <summary>
        /// [用于调试]输出指定控件的模板文本。仅用于调试。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void miWriteControlTemplate_Click(object sender, RoutedEventArgs e)
        {
            string templateText = GetTemplateXamlCode(mainTabControl.SelectedItem as Control);
            File.WriteAllText("D:\\控件模板.txt", templateText);
            LMessageBox.Show("控件模板文件已输出到：D:\\控件模板.txt");
        }

        /// <summary>
        /// [用于调试]输出指定控件的模板文本。仅用于调试。
        /// </summary>
        /// <param name="ctrl"></param>
        /// <returns></returns>
        string GetTemplateXamlCode(Control ctrl)
        {
            FrameworkTemplate template = ctrl.Template;
            string xaml = "";
            if (template != null)
            {
                XmlWriterSettings settings = new XmlWriterSettings();
                settings.Indent = true;
                settings.IndentChars = new string(' ', 4);
                settings.NewLineOnAttributes = true;
                StringBuilder strbuild = new StringBuilder();
                XmlWriter xmlwrite = XmlWriter.Create(strbuild, settings);
                try
                {
                    XamlWriter.Save(template, xmlwrite);
                    xaml = strbuild.ToString();
                }
                catch (Exception exc)
                {
                    xaml = exc.Message;
                }
            }
            else
            {
                xaml = "no template";
            }
            return xaml;
        }

        /// <summary>
        /// 旧的自定义主窗口标题栏，已无必要存在。
        /// </summary>
        private void dpTitle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                this.DragMove();
            }

            if (e.ClickCount == 2)
            {
                if (this.WindowState == WindowState.Maximized)
                {
                    this.WindowState = WindowState.Normal;
                }
                else if (this.WindowState == WindowState.Normal)
                {
                    this.WindowState = WindowState.Maximized;
                }
            }
        }

        private string defaultEncoding = "utf-8";
        /// <summary>
        /// 下次编译 html 时使用的默认编码。
        /// </summary>
        public string DefaultEncoding
        {
            get
            {
                return defaultEncoding;
            }
        }

        /// <summary>
        /// 设置下次编译 Html 时使用 UTF-8 编码。
        /// </summary>
        private void miUtf8_Click(object sender, RoutedEventArgs e)
        {
            SelectEncoding(miUtf8.Tag.ToString());
        }

        /// <summary>
        /// 设置下次编译 Html 时使用 GB2312 编码。
        /// </summary>
        private void miGb2312_Click(object sender, RoutedEventArgs e)
        {
            SelectEncoding(miGb2312.Tag.ToString());
        }

        /// <summary>
        /// 选取（设置）下一次编译 Html 文档时使用什么编码方式。
        /// </summary>
        /// <param name="encoding">目前只支持 GB2312 和 UTF-8 这两种编码。</param>
        private void SelectEncoding(string encoding)
        {
            RefreshEncoding(encoding);

            this.ConfigManager.Set("Encoding", encoding);
        }

        /// <summary>
        /// 根据“encoding”的值来刷新主界面上关于编译 Html 时使用的编码方式的两个菜单的选取状态。
        /// </summary>
        /// <param name="encoding"></param>
        private void RefreshEncoding(string encoding)
        {
            if (string.IsNullOrEmpty(encoding))
            {
                encoding = "utf-8";
            }

            if (encoding == "gb2312")
            {
                defaultEncoding = "gb2312";
                miUtf8.IsChecked = false;
                miGb2312.IsChecked = true;
            }
            else//都强制为UTF-8
            {
                if (encoding != "utf-8")
                {
                    encoding = "utf-8";
                }

                miUtf8.IsChecked = true;
                miGb2312.IsChecked = false;
                defaultEncoding = "utf-8";
            }
        }

        /// <summary>
        /// 为当前工作区创建一个 CHM 工程文件（包括对应的目录文件、索引文件）。创建后可以自动调用微软
        /// Html Help Workshop来进行编辑、编译 CHM 帮助文档的操作。
        /// </summary>
        private void miCreateCHMProject_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                var prefixOfFiles = Globals.PathOfWorkspace + Globals.WorkspaceShortName;
                var chmProjectFilePath = prefixOfFiles + ".hhp";
                var chmContentFilePath = prefixOfFiles + ".hhc";
                var chmIndexFilePath = prefixOfFiles + ".hhk";

                //var indexHtmlPath = "index.html";

                if (File.Exists(chmProjectFilePath) == true)
                {
                    var result = LMessageBox.Show("　　★请慎用此功能！！！\r\n　　它会将你之前创建的所有工程文件全部重置！！！\r\n\r\n" +
                        "　　所以，除非你从来没有在这个工作区使用过此功能，或者根本不介意之前自己手工修改的成果被全部抹杀——千万别用这个功能！\r\n\r\n" +
                        "　　你真的要继续吗？", Globals.AppName,
                         MessageBoxButton.YesNo, MessageBoxImage.Warning);
                    if (result != MessageBoxResult.Yes) return;
                }

                //先编译所有Markdown文档。
                var result2 = LMessageBox.Show("　　在创建 CHM 工程文件之前，应按 GB2312 编码方式对工作区中所有 Markdown 文件进行编译，要自动进行这步操作吗？", Globals.AppName,
                     MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (result2 == MessageBoxResult.Yes)
                {
                    miGb2312.IsChecked = true;
                    miGb2312_Click(sender, e);
                    CompileWholeWorkspace();
                }

                var gb2312 = Encoding.GetEncoding("gb2312");

                //第①步，生成hhp工程文件
                var sbProject = new StringBuilder();

                #region 工程文件内容

                sbProject.Append("[OPTIONS]" + "\r\n");
                sbProject.Append("Compatibility= 1.1 or later" + "\r\n");
                sbProject.Append("Compiled file = " + Globals.WorkspaceShortName + ".chm\r\n");
                sbProject.Append("Contents file = " + Globals.WorkspaceShortName + ".hhc\r\n");
                sbProject.Append("Default Font=新宋体,11,1\r\n");

                //这里还需要进一步润色。
                sbProject.Append("Default topic = _Index.html\r\n");

                sbProject.Append("Display compile progress = Yes" + "\r\n");
                sbProject.Append("Index file = " + Globals.WorkspaceShortName + ".hhk\r\n");
                sbProject.Append("Language = 0x804 中文(简体，中国)" + "\r\n");
                sbProject.Append("Title=" + Globals.WorkspaceShortName);

                sbProject.Append("\r\n\r\n");

                sbProject.Append("[FILES]" + "\r\n");
                sbProject.Append("_Index.html\r\n");
                sbProject.Append("Images~\\header_icon_dark.png\r\n");
                sbProject.Append("Images~\\header_icon_light.png\r\n");
                sbProject.Append("Images~\\comment_dark.png\r\n");
                sbProject.Append("Images~\\comment_light.png\r\n");
                sbProject.Append("Images~\\region_e_dark.png\r\n");
                sbProject.Append("Images~\\region_e_light.png\r\n");
                sbProject.Append("Images~\\region_i_dark.png\r\n");
                sbProject.Append("Images~\\region_i_light.png\r\n");
                sbProject.Append("Images~\\region_q_dark.png\r\n");
                sbProject.Append("Images~\\region_q_light.png\r\n");
                sbProject.Append("Images~\\region_w_dark.png\r\n");
                sbProject.Append("Images~\\region_w_light.png\r\n");

                sbProject.Append("\r\n");

                sbProject.Append("[INFOTYPES]" + "\r\n");

                #endregion

                File.WriteAllText(chmProjectFilePath, sbProject.ToString(), gb2312);

                //第②步，生成hhc目录文件
                var sbContent = new StringBuilder();

                #region 目录文件内容

                //目录文件头，包括首页（_Index.html）
                sbContent.Append("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\r\n");
                sbContent.Append("<HTML>\r\n");
                sbContent.Append("<HEAD>\r\n");
                sbContent.Append("<meta name=\"GENERATOR\" content=\"Microsoft&reg; HTML Help Workshop 4.1\">");
                sbContent.Append("<!--Sitemap 1.0-->\r\n");
                sbContent.Append("</HEAD><BODY>\r\n");
                sbContent.Append("<OBJECT type=\"text /site properties\">\r\n");
                sbContent.Append("\t<param name=\"ImageType\" value=\"Folder\">\r\n");
                sbContent.Append("</OBJECT>");

                //目录文件内容
                //如果是子目录，在子目录“< UL >”Tag前加上：
                //<LI> <OBJECT type="text/sitemap">
                //    < param name = "Name" value = "其它" >
                //    </ OBJECT >
                //有一层子目录就加一层“< UL >”Tag对

                var invalidatedFilePaths = CreateCHMContentFile(new DirectoryInfo(Globals.PathOfWorkspace), "", "", sbContent);

                //目录文件尾
                sbContent.Append("</BODY></HTML>\r\n");

                #endregion

                File.WriteAllText(chmContentFilePath, sbContent.ToString(), gb2312);

                //第③步，生成hhk索引文件
                var sbIndex = new StringBuilder();

                #region 索引文件内容

                //索引文件头，包括首页（_Index.html）
                sbIndex.Append("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\r\n");
                sbIndex.Append("<HTML>\r\n");
                sbIndex.Append("<HEAD>\r\n");
                sbIndex.Append("<meta name=\"GENERATOR\" content=\"Microsoft&reg; HTML Help Workshop 4.1\">\r\n");
                sbIndex.Append("<!--Sitemap 1.0-->\r\n");
                sbIndex.Append("</HEAD><BODY>\r\n");
                sbIndex.Append("<OBJECT type=\"text /site properties\">\r\n");
                sbIndex.Append("\t<param name=\"ImageType\" value=\"Folder\">\r\n");
                sbIndex.Append("</OBJECT>\r\n");
                sbIndex.Append("<UL>\r\n");//索引文件不分层，所以不在内部添加<UL>标签。

                //索引文件内容
                //如果是子目录，在子目录“< UL >”Tag前加上：
                //<LI><OBJECT type="text/sitemap">
                //    <param name = "Name" value = "其它">
                //    </OBJECT >
                //有一层子目录就加一层“<UL>”Tag对

                CreateCHMIndexFile(new DirectoryInfo(Globals.PathOfWorkspace), "", sbIndex);

                //索引文件尾
                sbIndex.Append("</UL>\r\n");//索引文件不分层，所以不在内部添加<UL>标签。
                sbIndex.Append("</BODY></HTML>\r\n");

                #endregion

                File.WriteAllText(chmIndexFilePath, sbIndex.ToString(), gb2312);

                //第④步，调用hhc.exe编译
                //既然已经内置了编辑器，为什么不直接用它来手工修改工程文件再编译呢？

                if (invalidatedFilePaths.Count > 0)
                {
                    StringBuilder sb = new StringBuilder();
                    foreach (string ifp in invalidatedFilePaths)
                    {
                        sb.Append("　　" + ifp + "\r\n");
                    }
                    LMessageBox.Show("　　指定的工作区目录中下列文件的物理路径中含有特殊字符，会导致编译的 CHM 文件中的链接失效！\r\n\r\n"
                        + sb.ToString(), Globals.AppName,
                         MessageBoxButton.OK, MessageBoxImage.Warning);
                }

                //编译总是会出错，所以干脆直接集成了Html Help Workshop到安装包里。
                LoadHHWInstalledPath();

                if (File.Exists(this.HHWInstalledPath))
                {
                    //System.Diagnostics.Process.Start(this.HHWInstalledPath, $"\"{chmProjectFilePath}\"");
                    //加引号的办法虽然可以解决路径带空格的问题，但在 Html Help Workshop 中点击“编译”按钮时
                    //在弹出的路径选取框中获得的路径也会带双引号——这时又会弹出“找不到路径”的错误框。

                    //下面这个办法也不行，这貌似是 Linux 下的办法？
                    //if (chmProjectFilePath.Contains(" "))
                    //{
                    //    chmProjectFilePath = chmProjectFilePath.Replace(" ", "\\ ");
                    //}

                    if (chmProjectFilePath.Contains(" "))
                    {
                        LMessageBox.Show("　　您设置的工作区目录完整路径中含有空格，这会导致 Html Help Workshop 在编译时提示无法打开工程文件！\r\n　　请在弹出该消息框后手工选取工程文件位置再编译（办法是去除路径首尾自动添加的双引号）。",
                            Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                        System.Diagnostics.Process.Start(this.HHWInstalledPath, $"\"{chmProjectFilePath}\"");
                    }
                    else
                    {
                        System.Diagnostics.Process.Start(this.HHWInstalledPath, chmProjectFilePath);
                    }
                }
                else
                {
                    LMessageBox.Show("　　未指定 Microsoft HTML Help Workshop.exe 的磁盘路径，无法调用！\r\n" +
                        "　　您可能需要到微软公司官方网站下载该工具并安装。",
                        Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
                }

                //try
                //{
                //    //由于物理路径很可能包括空格，所以必须用双引号括起来，不然在命令行中会出错。
                //    //var cmd = "\"" + Globals.HtmlHelpCompilerFullPath + "\" \"" + chmProjectFilePath + "\"";

                //    System.Diagnostics.Process.Start(Globals.HtmlHelpCompilerFullPath, $"\"{chmProjectFilePath}\"");
                //}
                //catch (Exception ex)
                //{
                //    LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
                //}
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
            }
        }

        /// <summary>
        /// 载入 Html Help Workshop 的安装位置信息。
        /// </summary>
        private void LoadHHWInstalledPath()
        {
            if (File.Exists(this.HHWInstalledPath) == false)
            {
                var result = LMessageBox.Show("　　请指定 Microsoft Html Help Workshop 所在的磁盘路径：\r\n" +
                    "　　通常它的文件名应是 Microsoft HTML Help Workshop.exe 。\r\n\r\n　　要现在指定吗？", Globals.AppName,
                     MessageBoxButton.YesNo, MessageBoxImage.Information);

                if (result != MessageBoxResult.Yes) return;
                SetHtmlHelpWorkshopFullName();
            }
        }

        /// <summary>
        /// 指定安装的 Html Help Workshop 的路径。
        /// 程序会自动通过注册表寻找安装的 Html Help Workshop。
        /// 但有时我们下载的不是微软公司的官方版本，很可能是网友修改过的汉化版——此时注册表中未必有这个安装位置信息。
        /// </summary>
        private void SetHtmlHelpWorkshopFullName()
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Multiselect = false;
            ofd.Title = Globals.AppName + "请指定 Microsoft Html Help Workshop 路径：";
            ofd.Filter = "可执行文件(*.exe)|*.exe";

            if (ofd.ShowDialog() == true)
            {
                this.ConfigManager.Set("HHWInstalledPath", ofd.FileName);
                this.hhwInstalledPath = ofd.FileName;
            }
        }

        /// <summary>
        /// 与InputBox中的同名方法不同：这里要考虑路径分割符和盘符。
        /// </summary>
        /// <param name="text">要检查的源文本。</param>
        /// <param name="highLevel">是否只允许字母、数字、下划线字符。</param>
        /// <returns>为真表示可以用作文件名。</returns>
        public static bool ValidateFilePath(string text, bool highLevel)
        {
            if (string.IsNullOrEmpty(text))
                return false;

            if (highLevel)
            {
                return Regex.Match(text, @"([a-zA-Z]:\\){0,1}([a-zA-Z0-9_\.]\\{1,})").Success;
            }

            if (text.Contains("/") ||
               //text.Contains("\\") ||     //路径中必须带分隔符，故这行不能加。
               //text.Contains(":") ||      //完整的路径中带盘符，故冒号不能加。
               text.Contains("*") ||
               text.Contains("\"") ||
               text.Contains("<") ||
               text.Contains(">") ||
               text.Contains("|"))
            {
                return false;
            }

            //带冒号的路径要考虑盘符的问题。
            if (text.Contains(":") || text.Contains("\\"))
            {
                if (text.Length <= 3)
                    return false;

                var subText1 = text.Substring(0, 3);
                if (subText1.EndsWith(":\\") == false)
                    return false;
                var firstChar = subText1[0];
                if (((firstChar >= 'a' && firstChar <= 'z') || (firstChar >= 'A' && firstChar <= 'Z')) == false)
                    return false;
                //以上可以确定不是盘符

                var subText2 = text.Substring(3);
                if (subText2.Contains(":"))
                    return false;//除盘符外，其它位置不应存在冒号

                if (subText2.StartsWith("\\") || subText2.EndsWith("\\"))
                    return false;
                //除开盘符后，路径的剩余部分不应以反斜杠开头；
                //即使不是以反斜杠开头，也仍然可能是个目录（目录以反斜杠结尾）
            }


            for (int i = 0; i < text.Length; i++)
            {
                char c = text[i];
                if (i == 2 && c == ':') continue;

                //全角标点均可用，半角标点不允许前列特殊字符。另，
                //半角波形符用于资源文件夹，不可用。
                //下划线用于目录元文件，不可用。
                //反引号符备用，不可用。

                //与InputBox不同，这里要允许冒号（盘符要用冒号）。
                if (@"""#%&()+,;<=>?@|".IndexOf(c) >= 0) return false;

                //与InputBox下的同名方法不同：这里要允许存在路径分隔符和冒号（盘符要用）
                if ("/\\!@$^-_[]{}',.～·＠＃￥％…＆＊（）【】[]｛｝｜；‘’“”：，。《〈〉》？".IndexOf(c) >= 0) continue;

                //全角字符就算是特殊符号也是可以的
                //全角小写字母
                if (c == '、') continue;
                if (c >= 'ａ' && c <= 'ｚ') continue;
                //全角大写字母
                if (c >= 'Ａ' && c <= 'Ｚ') continue;
                //！＂＃＄％＆＇（）＊＋，－．／
                if (c >= '！' && c <= '／') continue;
                //全角数字
                if (c >= '０' && c <= '９') continue;
                //：；＜＝＞？＠
                if (c >= '：' && c <= '＠') continue;
                //［＼］＾＿｀
                if (c >= '［' && c <= '｀') continue;
                //｛｜｝～
                if (c >= '｛' && c <= '～') continue;
                //￠￡￢￣￤￥
                if (c >= '￠' && c <= '￥') continue;
                //全角字符就算是特殊符号也是可以的

                //全角问号？就是>0x9fbb的
                if (c > 0x9fbb)
                {
                    if (c == '§') continue;
                    if (c == '☆') continue;
                    if (c == '★') continue;
                    if (c == '○') continue;
                    if (c == '●') continue;
                    if (c == '◎') continue;
                    if (c == '◇') continue;
                    if (c == '◆') continue;
                    if (c == '□') continue;
                    if (c == '■') continue;
                    if (c == '△') continue;
                    if (c == '▲') continue;
                    if (c == '※') continue;
                    if (c == '〓') continue;
                    if (c == '＃') continue;
                    if (c == '＆') continue;
                    if (c == '＠') continue;
                    if (c == '＿') continue;
                    if (c == '￣') continue;

                    return false;
                }

                if (c < 0x4e00)
                {
                    if (c == '\\') continue;
                    if (c == ':') continue;
                    if (c >= '0' && c <= '9') continue;
                    if (c == '_') continue;
                    if (c == '+') continue;
                    if (c == '-') continue;
                    if (c == '.') continue;//后缀名需要
                    if (c == '=') continue;
                    if (c == ' ') continue;
                    if (c == '(') continue;
                    if (c == ')') continue;
                    if (c == '）') continue;
                    if (c == '（') continue;
                    if (c >= '⑴' && c <= '⒇') continue;
                    if (c >= '①' && c <= '⑩') continue;
                    if (c >= '⒈' && c <= '⒛') continue;
                    if (c >= 'A' && c <= 'Z') continue;
                    if (c >= 'a' && c <= 'z') continue;

                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// 生成CHM工程所需要的目录文件。
        /// </summary>
        /// <param name="directoryInfo"></param>
        /// <param name="prefixTab">第一层应传入空字符串，不需要\t。</param>
        /// <param name="prefixPath">表示目录层级关系的关缀文本。</param>
        /// <param name="sBuilder">文本缓存。</param>
        /// <returns>返回带特殊字符的物理路径列表。</returns>
        private List<string> CreateCHMContentFile(DirectoryInfo directoryInfo, string prefixTab, string prefixPath, StringBuilder sBuilder)
        {
            if (directoryInfo == null || directoryInfo.Exists == false || prefixPath == null || sBuilder == null)
                return new List<string>();//返回空列表，避免返回null。

            if (prefixPath != "" && prefixPath.EndsWith("/") == false) prefixPath += "/";

            sBuilder.Append($"{prefixTab}<UL>\r\n");

            List<string> invalidatedFilePaths = new List<string>();

            List<ChmContentEntry> chmContentEntries = new List<ChmContentEntry>();

            var files = directoryInfo.GetFiles();
            if (files.Length > 0)
            {
                foreach (var fileInfo in files)
                {
                    if (fileInfo.FullName.ToLower().EndsWith(".html") == false) continue;
                    if (fileInfo.FullName.ToLower().EndsWith("~.html")) continue;

                    if (fileInfo.Name.ToLower() == ("_" + directoryInfo + ".html").ToLower()) continue;//这个文件是指向CHM目录节点的文件。不在这里添加目录引用。

                    if (ValidateFilePath(fileInfo.FullName, true) == false)
                    {
                        invalidatedFilePaths.Add(fileInfo.FullName);
                    }

                    string contentEntryName = GetTitleOfMdFile(fileInfo.FullName);

                    if (string.IsNullOrEmpty(contentEntryName))
                    {
                        var shortName = fileInfo.Name;
                        if (shortName.StartsWith("_") && shortName.Length > 1)
                        {
                            shortName = shortName.Substring(1);
                        }

                        if (shortName.ToLower() == "index.html" || shortName.ToLower() == "_index.html")
                        {
                            shortName = "目录";
                        }
                        else
                        {
                            if (shortName.ToLower().EndsWith(".html") && shortName.Length > 5)
                            {
                                shortName = shortName.Substring(0, shortName.Length - 5);
                            }
                        }

                        contentEntryName = shortName;
                    }

                    chmContentEntries.Add(new ChmContentEntry()
                    {
                        FileSystemInfo = fileInfo,
                        ShortName = fileInfo.Name,
                        Title = contentEntryName,
                        IsFile = true,
                    });
                }
            }

            var subDirectories = directoryInfo.GetDirectories();
            if (subDirectories.Length > 0)
            {
                //prefixTab += "\t";
                foreach (var subDirectory in subDirectories)
                {
                    if (subDirectory.FullName.EndsWith("~")) continue;

                    var subFiles = subDirectory.GetFiles();
                    var htmlCount = 0;
                    string directoryMetaFile = null;
                    foreach (var subfile in subFiles)
                    {
                        if (subfile.FullName.ToLower().EndsWith(".html"))
                        {
                            htmlCount++;
                            if (subfile.Name.ToLower() == ("_" + subDirectory.Name + ".html").ToLower())
                            {
                                directoryMetaFile = subfile.Name;
                            }
                        }
                    }

                    chmContentEntries.Add(new ChmContentEntry()
                    {
                        IsDirectory = true,
                        FileSystemInfo = subDirectory,
                        ShortName = subDirectory.Name,
                        Title = subDirectory.Name,
                        DirectoryMetaFile = directoryMetaFile,
                    });

                }
            }

            List<ChmContentEntry> tempList2 = new List<ChmContentEntry>();
            List<ChmContentEntry> tempList3 = new List<ChmContentEntry>();

            foreach (var c in chmContentEntries)
            {
                if (c.ShortName.StartsWith("_"))
                {
                    tempList2.Add(c);
                }
                else
                {
                    tempList3.Add(c);
                }
            }

            tempList2.Sort(new ChmContentEntryCompare());
            tempList3.Sort(new ChmContentEntryCompare());

            foreach (var ce in tempList2)
            {
                if (ce.IsFile)
                {
                    sBuilder.Append($"{prefixTab}<LI> <OBJECT type = \"text/sitemap\">\r\n");
                    sBuilder.Append($"{prefixTab}\t<param name =\"Name\" value=\"{ce.Title}\">\r\n");
                    sBuilder.Append($"{prefixTab}\t<param name =\"Local\" value=\"{CustomMarkdownSupport.UrlEncode(prefixPath)}{ce.UrlName}\">\r\n");
                    sBuilder.Append($"{prefixTab}\t</OBJECT>\r\n");
                }
                else if (ce.IsDirectory)
                {
                    prefixTab += "\t";
                    //先加目录，即使是空目录也应该添加一条目录条目
                    sBuilder.Append($"{prefixTab}<LI> <OBJECT type = \"text/sitemap\">\r\n");
                    sBuilder.Append($"{prefixTab}\t<param name =\"Name\" value=\"{ce.UrlName}\">\r\n");

                    //目录节点在CHM中指向的文件应是该目录下名为“_目录短名.html”的文件。
                    if (string.IsNullOrWhiteSpace(ce.DirectoryMetaFile) == false)
                    {
                        sBuilder.Append($"{prefixTab}\t<param name =\"Local\" value=\"{prefixPath}{ce.UrlName}\\{ce.DirectoryMetaFile}\">\r\n");
                    }

                    sBuilder.Append($"{prefixTab}\t</OBJECT>\r\n");

                    var subPrefixPath = prefixPath + ce.UrlName;

                    //因为是递归的，所以不能直接用下面这行：
                    //hasInvalidatedFilePath = CreateContentDirectory(subDirectiry, prefixTab, subPrefixPath, sb);

                    var subInvalidatedFilePaths = CreateCHMContentFile(ce.FileSystemInfo as DirectoryInfo, prefixTab, subPrefixPath, sBuilder);
                    if (subInvalidatedFilePaths.Count > 0)
                    {
                        invalidatedFilePaths.AddRange(subInvalidatedFilePaths);
                    }
                    prefixTab = prefixTab.Substring(0, prefixTab.Length - 1);
                }
            }

            foreach (var ce in tempList3)
            {
                if (ce.IsFile)
                {
                    sBuilder.Append($"{prefixTab}<LI> <OBJECT type = \"text/sitemap\">\r\n");
                    sBuilder.Append($"{prefixTab}\t<param name =\"Name\" value=\"{ce.Title}\">\r\n");
                    sBuilder.Append($"{prefixTab}\t<param name =\"Local\" value=\"{prefixPath}{ce.UrlName}\">\r\n");
                    sBuilder.Append($"{prefixTab}\t</OBJECT>\r\n");
                }
                else if (ce.IsDirectory)
                {
                    prefixTab += "\t";
                    //先加目录，即使是空目录也应该添加一条目录条目
                    sBuilder.Append($"{prefixTab}<LI> <OBJECT type = \"text/sitemap\">\r\n");
                    sBuilder.Append($"{prefixTab}\t<param name =\"Name\" value=\"{FormatDocumentTitle(ce.UrlName)}\">\r\n");

                    //目录节点在CHM中指向的文件应是该目录下名为“_目录短名.html”的文件。
                    if (string.IsNullOrWhiteSpace(ce.DirectoryMetaFile) == false)
                    {
                        sBuilder.Append($"{prefixTab}\t<param name =\"Local\" value=\"{prefixPath}{ce.UrlName}\\{ce.DirectoryMetaFile}\">\r\n");
                    }

                    sBuilder.Append($"{prefixTab}\t</OBJECT>\r\n");

                    var subPrefixPath = prefixPath + ce.UrlName;

                    //因为是递归的，所以不能直接用下面这行：
                    //invalidatedFilePaths = CreateContentDirectory(subDirectiry, prefixTab, subPrefixPath, sb);

                    var subInvalidatedFilePaths = CreateCHMContentFile(ce.FileSystemInfo as DirectoryInfo, prefixTab, subPrefixPath, sBuilder);
                    if (invalidatedFilePaths.Count > 0)
                    {
                        invalidatedFilePaths.AddRange(subInvalidatedFilePaths);
                    }
                    prefixTab = prefixTab.Substring(0, prefixTab.Length - 1);
                }
            }

            sBuilder.Append($"{prefixTab}</UL>\r\n");
            return invalidatedFilePaths;
        }


        /// <summary>
        /// 生成CHM工程所需要的索引文件。与目录文件不同，索引文件不分层级。
        /// </summary>
        /// <param name="directoryInfo">基准目录，通常应传入工作区目录。</param>
        /// <param name="prefixPath">表示目录层级关系的前缀。</param>
        /// <param name="sBuilder">文本缓存。</param>
        /// <returns>返回含有特殊字符的文件路径的列表。</returns>
        private List<string> CreateCHMIndexFile(DirectoryInfo directoryInfo, string prefixPath, StringBuilder sBuilder)
        {
            if (directoryInfo == null || directoryInfo.Exists == false || prefixPath == null || sBuilder == null)
                return new List<string>();

            if (prefixPath != "" && prefixPath.EndsWith("/") == false) prefixPath += "/";

            List<string> invalidatedFilePaths = new List<string>();

            var files = directoryInfo.GetFiles();
            if (files.Length > 0)
            {
                foreach (var fileInfo in files)
                {
                    if (fileInfo.FullName.ToLower().EndsWith(".html") == false) continue;
                    if (fileInfo.FullName.ToLower().EndsWith("~.html")) continue;

                    if (ValidateFilePath(fileInfo.FullName, true) == false)
                    {
                        invalidatedFilePaths.Add(fileInfo.FullName);
                    }

                    string contentEntryName = GetTitleOfMdFile(fileInfo.FullName);

                    if (string.IsNullOrEmpty(contentEntryName))
                    {
                        var shortName = fileInfo.Name;
                        if (shortName.StartsWith("_") && shortName.Length > 1)
                        {
                            shortName = shortName.Substring(1);
                        }

                        if (shortName.ToLower() == "index.html" || shortName.ToLower() == "_index.html")
                        {
                            shortName = "目录";
                        }
                        else
                        {
                            if (shortName.ToLower().EndsWith(".html") && shortName.Length > 5)
                            {
                                shortName = shortName.Substring(0, shortName.Length - 5);
                            }
                        }

                        contentEntryName = shortName;
                    }

                    sBuilder.Append("\t<LI> <OBJECT type = \"text/sitemap\">\r\n");
                    sBuilder.Append($"\t\t<param name =\"Name\" value=\"{CustomMarkdownSupport.UrlEncode(contentEntryName)}\">\r\n");
                    sBuilder.Append($"\t\t<param name =\"Local\" value=\"{prefixPath}{CustomMarkdownSupport.UrlEncode(fileInfo.Name)}\">\r\n");
                    sBuilder.Append("\t\t</OBJECT>\r\n");
                }
            }

            var subDirectories = directoryInfo.GetDirectories();
            if (subDirectories.Length > 0)
            {
                foreach (var subDirectiry in subDirectories)
                {
                    if (subDirectiry.FullName.EndsWith("~")) continue;

                    var subFiles = subDirectiry.GetFiles();
                    var htmlCount = 0;
                    foreach (var subfile in subFiles)
                    {
                        if (subfile.FullName.ToLower().EndsWith(".html")) htmlCount++;
                    }

                    if (htmlCount > 0)
                    {
                        //索引文件不加目录
                        //sb.Append($"\t<LI> <OBJECT type = \"text/sitemap\">\r\n");
                        //sb.Append($"\t\t<param name =\"Name\" value=\"{subDirectiry.Name}\">\r\n");
                        //sb.Append($"\t\t</OBJECT>\r\n");

                        var subPrefixPath = CustomMarkdownSupport.UrlEncode(subDirectiry.FullName);

                        //因为是递归的，所以不能直接用下面这行：
                        //hasInvalidatedFilePath = CreateContentDirectory(subDirectiry, prefixTab, subPrefixPath, sb);

                        var subInvalidatedFilePaths = CreateCHMIndexFile(subDirectiry, subPrefixPath, sBuilder);
                        if (subInvalidatedFilePaths.Count > 0)
                        {
                            invalidatedFilePaths.AddRange(subInvalidatedFilePaths);
                        }
                    }
                }
            }

            return invalidatedFilePaths;
        }

        /// <summary>
        /// 根据指定的 Markdown 文件读取其中的标题文本。
        /// 标题文本通常是第一个以“%”开头的行，如找不到，则寻找被“标题＞＞”和“＜＜标题”包围的文本作为标题。
        /// </summary>
        public static string GetTitleOfMdFile(string mdFileFullName)
        {
            if (mdFileFullName != null && mdFileFullName.ToLower().EndsWith(".html"))
            {
                mdFileFullName = mdFileFullName.Substring(0, mdFileFullName.Length - 5) + ".md";
            }

            if (File.Exists(mdFileFullName) == false) return "";

            var lines = File.ReadLines(mdFileFullName);
            foreach (string s in lines)
            {
                if (s.StartsWith("%")) return s.Substring(1).Trim();
                var title = CustomMarkdownSupport.GetDocumentTitle(s);
                if (string.IsNullOrWhiteSpace(title) == false) return title;

                int startIndex = s.IndexOf("标题＞＞");
                int endIndex = s.IndexOf("＜＜标题");
                if (startIndex >= 0 && endIndex >= 0)
                {
                    return s.Substring(startIndex + 4, endIndex - startIndex - 4);
                }
            }

            return "";
        }

        /// <summary>
        /// 编译工作区及其下级目录下的所有 Markdown 文档。
        /// </summary>
        private void miCompileAllMdFilesOfWorkspace_Click(object sender, RoutedEventArgs e)
        {
            if (miUtf8.IsChecked)
            {
                var result = LMessageBox.Show("　　当前选择的字符编码是UTF-8，这种编码的Html不适合编译成CHM文档。要自动改成GB2312字符编码吗？",
                                    Globals.AppName, MessageBoxButton.YesNoCancel, MessageBoxImage.Warning);

                if (result == MessageBoxResult.Cancel) return;
                if (result == MessageBoxResult.OK)
                {
                    miGb2312.IsChecked = true;
                    miGb2312_Click(sender, e);
                }
            }

            CompileWholeWorkspace();

            var charsetText = "GB2312";
            if (miUtf8.IsChecked)
            {
                charsetText = "UTF-8";
            }

            var indexHtmlFilePath = Globals.PathOfWorkspace + "_index.html";

            if (File.Exists(indexHtmlFilePath))
            {
                if (previewFrame.Source != null && previewFrame.Source.AbsolutePath != indexHtmlFilePath)
                {
                    previewFrame.Refresh();
                }
                else
                {
                    previewFrame.Source = new Uri(indexHtmlFilePath);
                }

                rightToolBar.SelectedIndex = 0;
                if (rightToolBar.Visibility != System.Windows.Visibility.Visible ||
                    rightToolBar.ActualWidth <= 0)
                {
                    SwitchRightToolBarToggle();
                }
            }

            LMessageBox.Show($"　　已按照【{charsetText}】编码将工作区内所有 Markdown 文件编译为 Html 文件！",
                Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
        }

        /// <summary>
        /// 编译全工作区（含下级目录）下的所有 Markdown 文件。
        /// </summary>
        private void CompileWholeWorkspace()
        {
            List<FileSystemEntry> fileSystemEntries = new List<FileSystemEntry>();

            CompileAllMdFiles(ref fileSystemEntries);

            //生成一个html索引文档。
            BuildIndexOfCompiledHtmlFiles(fileSystemEntries);
        }

        /// <summary>
        /// 打开创建好的属于当前工作区的 CHM 工程文件。
        /// </summary>
        private void miOpenCHMProject_Click(object sender, RoutedEventArgs e)
        {
            LoadHHWInstalledPath();

            if (File.Exists(this.HHWInstalledPath) == false)
            {
                LMessageBox.Show("　　未指定 Microsoft HTML Help Workshop.exe 的磁盘路径，无法调用！\r\n" +
                    "　　您可能需要到微软公司官方网站下载该工具并安装。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            var prefixOfFiles = Globals.PathOfWorkspace + Globals.WorkspaceShortName;
            var chmProjectFilePath = prefixOfFiles + ".hhp";

            if (File.Exists(chmProjectFilePath))
            {
                //System.Diagnostics.Process.Start(this.HHWInstalledPath, $"\"{chmProjectFilePath}\"");
                //加引号的办法虽然可以解决路径带空格的问题，但在 Html Help Workshop 中点击“编译”按钮时
                //在弹出的路径选取框中获得的路径也会带双引号——这时又会弹出“找不到路径”的错误框。

                //下面这个办法也不行，这貌似是 Linux 下的办法？
                //if (chmProjectFilePath.Contains(" "))
                //{
                //    chmProjectFilePath = chmProjectFilePath.Replace(" ", "\\ ");
                //}

                //事实证明，给每个子目录或文件名（带空格）分别包围双引号的办法也行不通。
                //只能禁止工作区目录和用户创建的所有目录带空格了，还要提示用户选择的路径带空格。

                if (chmProjectFilePath.Contains(" "))
                {
                    LMessageBox.Show("　　您设置的工作区目录完整路径中含有空格，这会导致 Html Help Workshop 在编译时提示无法打开工程文件！\r\n　　请在弹出该消息框后手工选取工程文件位置再编译（办法是去除路径首尾自动添加的双引号）。",
                        Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    System.Diagnostics.Process.Start(this.HHWInstalledPath, $"\"{chmProjectFilePath}\"");
                }
                else
                {
                    System.Diagnostics.Process.Start(this.HHWInstalledPath, chmProjectFilePath);
                }
            }
            else
            {
                LMessageBox.Show("　　尚未创建工程。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 清理工作区及其下级目录中所有 Html 文件（无论是不是由 LME 生成的）。
        /// </summary>
        private void miClearCompiledHtmlFiles_Click(object sender, RoutedEventArgs e)
        {
            var result = LMessageBox.Show("　　此功能将删除工作区目录下所有Html文件，无论它是不是由本程序编译而成！\r\n\r\n　　真的要继续吗？",
                Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Warning);

            if (result != MessageBoxResult.Yes) return;

            DeleteHtmlFiles(Globals.PathOfWorkspace);
        }

        /// <summary>
        /// 清理指定目录及其下级目录中所有 Html 文件（无论是不是由 LME 生成的）。
        /// </summary>
        /// <param name="directoryPath">基准目录。从此目录开始递归清理。</param>
        private void DeleteHtmlFiles(string directoryPath)
        {
            if (Directory.Exists(directoryPath) == false) return;
            var directoryShortName = Path.GetDirectoryName(directoryPath);
            if (directoryShortName.EndsWith("~")) return;

            var directoryInfo = new DirectoryInfo(directoryPath);
            var files = directoryInfo.GetFiles();
            foreach (var file in files)
            {
                if (file.Extension.ToLower() != ".html") continue;
                if (Path.GetFileNameWithoutExtension(file.FullName).EndsWith("~")) continue;

                File.Delete(file.FullName);
            }

            var subDirectiries = directoryInfo.GetDirectories();
            foreach (var subDirectory in subDirectiries)
            {
                DeleteHtmlFiles(subDirectory.FullName);
            }
        }

        /// <summary>
        /// 切换“填空模式（FillBlank）”的开关。此属性用以决定是否将行内代码片段编译为带填空题的效果。
        /// 即是否令 Html 的 <code></code> 块初始前景色为透明——用户点击后再更改为其它色彩。
        /// </summary>
        private void miFillblankMode_Click(object sender, RoutedEventArgs e)
        {
            this.compileCodeToFillBlank =
            miFillblankMode.IsChecked = !miFillblankMode.IsChecked;
            this.ConfigManager.Set("CompileCodeToFillBlank", miFillblankMode.IsChecked.ToString());
        }

        /// <summary>
        /// 删除历史工作区列表中选定条目。
        /// </summary>
        private void miDeleteHistoryWorkspaceEntry_Click(object sender, RoutedEventArgs e)
        {
            if (lbxHistoryWorkspaces.SelectedIndex < 0)
            {
                LMessageBox.Show("　　请先选择要删除的条目！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            lbxHistoryWorkspaces.Items.RemoveAt(lbxHistoryWorkspaces.SelectedIndex);

            StringBuilder sb = new StringBuilder();
            foreach (RecentDirectoryListBoxItem item in lbxHistoryWorkspaces.Items)
            {
                sb.Insert(0, "\r\n" + item.DirectoryPath);
            }

            File.WriteAllText(Globals.PathOfHistoryWorkspaceFileFullName, sb.ToString(), new UnicodeEncoding());
        }

        /// <summary>
        /// 删除历史导出区列表中选定的目录条目。
        /// </summary>
        private void miDeleteHistoryOutputEntry_Click(object sender, RoutedEventArgs e)
        {
            if (lbxHistoryOutport.SelectedIndex < 0)
            {
                LMessageBox.Show("　　请先选择要删除的条目！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            lbxHistoryOutport.Items.RemoveAt(lbxHistoryOutport.SelectedIndex);

            StringBuilder sb = new StringBuilder();
            foreach (RecentDirectoryListBoxItem item in lbxHistoryOutport.Items)
            {
                sb.Insert(0, "\r\n" + item.DirectoryPath);
            }

            File.WriteAllText(Globals.PathOfHistoryOutputFileFullName, sb.ToString(), new UnicodeEncoding());
        }

        /// <summary>
        /// 清除“历史工作区（最近工作区列）”列表。
        /// </summary>
        private void miDeleteHistoryWorkspaceFile_Click(object sender, RoutedEventArgs e)
        {
            if (lbxHistoryWorkspaces.Items.Count <= 0) return;

            var result = LMessageBox.Show("　　真的要清除“最近工作区列表”吗？", Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
            if (result != MessageBoxResult.Yes) return;

            try
            {
                if (File.Exists(Globals.PathOfHistoryWorkspaceFileFullName))
                {
                    File.Delete(Globals.PathOfHistoryWorkspaceFileFullName);
                }

                lbxHistoryWorkspaces.Items.Clear();
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 清除“历史导出区”列表。
        /// </summary>
        private void miDeleteHistoryOutputFile_Click(object sender, RoutedEventArgs e)
        {
            if (lbxHistoryOutport.Items.Count <= 0) return;

            var result = LMessageBox.Show("　　真的要清除“历史导出目录列表”吗？",
                Globals.AppName, MessageBoxButton.YesNo, MessageBoxImage.Question);
            if (result != MessageBoxResult.Yes) return;

            try
            {
                if (File.Exists(Globals.PathOfHistoryOutputFileFullName))
                {
                    File.Delete(Globals.PathOfHistoryOutputFileFullName);
                }

                lbxHistoryOutport.Items.Clear();
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName,
                    MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 在当前活动编辑器当前位置插入一个“锚”。形如：[](@)。
        /// </summary>
        private void miInsertAnchor_Click(object sender, RoutedEventArgs e)
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            var selText = eti.EditorBase.SelectedText;
            if (selText.Length > 0)
            {
                eti.EditorBase.SelectedText = $"\r\n\r\n[](@{selText})\r\n\r\n";
                var destSel = eti.EditorBase.SelectionStart + 8;
                if (destSel < eti.EditorBase.Document.TextLength)
                {
                    eti.EditorBase.Select(destSel, selText.Length);
                }
            }
            else
            {
                eti.EditorBase.SelectedText = "\r\n\r\n[](@请输入锚名称)\r\n\r\n";
                var destSel = eti.EditorBase.SelectionStart + 8;
                if (destSel < eti.EditorBase.Document.TextLength)
                {
                    eti.EditorBase.Select(destSel, 6);
                }
            }
        }

        /// <summary>
        /// 在当前活动编辑器当前位置插入链接标记文本。形如：[]()。
        /// </summary>
        private void miInsertLinkMark_Click(object sender, RoutedEventArgs e)
        {
            MarkdownEditor eti = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (eti == null) return;

            var selText = eti.EditorBase.SelectedText;
            if (selText.Length > 0)
            {
                eti.EditorBase.SelectedText = $"[{selText}]()";
                var destSel = eti.EditorBase.SelectionStart + 3 + selText.Length;
                if (destSel < eti.EditorBase.Document.TextLength)
                {
                    eti.EditorBase.Select(destSel, 0);
                }
            }
            else
            {
                eti.EditorBase.SelectedText = "[请输入链接名称]()";
                var destSel = eti.EditorBase.SelectionStart + 1;
                if (destSel < eti.EditorBase.Document.TextLength)
                {
                    eti.EditorBase.Select(destSel, 7);
                }
            }
        }

        /// <summary>
        /// 窗口最大化。
        /// </summary>
        private void miMaxSizeWindow_Click(object sender, RoutedEventArgs e)
        {
            this.WindowState = WindowState.Maximized;
        }

        /// <summary>
        /// 切换“自动折行（TextAutoWrap）”的开关。开启时编辑区文本过长时会自动折行，否则总是在一行（显示横向滚动条）。
        /// </summary>
        private void miTextWrap_Click(object sender, RoutedEventArgs e)
        {
            this.TextAutoWrap =
            this.miTextAutoWrap.IsChecked = !this.miTextAutoWrap.IsChecked;
            this.configManager.Set("TextAutoWrap", miTextAutoWrap.IsChecked.ToString());
        }

        /// <summary>
        /// 打开工作区管理器中选定条目所对应的 Markdown 文件、图像文件、目录。
        /// 图像文件、目录会调用 Windows 资源管理器打开；Markdown 文件则在右侧编辑区打开。
        /// </summary>
        private void miOpenFileOrFolder_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("　　请先选中工作区目录中某一个条目！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (wtvi.IsMarkdownFilePath)
            {
                OpenDocuments(new string[] { wtvi.FullPath });
            }
            else if (wtvi.IsDirectoryExists || wtvi.IsImageFileExist)
            {
                System.Diagnostics.Process.Start("explorer.exe", $"\"{wtvi.FullPath}\"");
            }
            else
            {
                LMessageBox.Show("　　只有Markdown文档或目录或图像文件才可以打开！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 打开工作区管理器中选定的 Markdown 文档。
        /// 如果选中的条目是指向一个目录（但不是资源目录）且尚未创建该目录的元文件，则自动创建一个目录元文件。
        /// </summary>
        private void miOpenDocument_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("　　请先选中工作区目录中某一个条目！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            //如果是MD文件，直接打开；
            //如果是Directory，打开它的默认MD文件；
            //如果是Image，调用Explorer使用系统默认图片应用程序打开。

            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (wtvi.IsMarkdownFilePath)
            {
                OpenDocuments(new string[] { wtvi.FullPath });
            }
            else if (wtvi.IsDirectoryExists)
            {
                try
                {
                    DirectoryInfo di = new DirectoryInfo(wtvi.FullPath);
                    if (di.Name.EndsWith("~"))
                    {
                        LMessageBox.Show("　　以波型符结尾的是资源文件夹，不允许创建对应 Markdown 元文件。", Globals.AppName,
                          MessageBoxButton.OK, MessageBoxImage.Warning);
                        return;
                    }
                    var directoryMdFileFullName = (di.FullName.EndsWith("\\") ? di.FullName : (di.FullName + "\\")) + "_" + di.Name + ".md";
                    CreateDirectoryMetaMdFile(di, directoryMdFileFullName);

                    OpenDocuments(new string[] { directoryMdFileFullName });
                }
                catch (Exception ex)
                {
                    LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace);
                }
            }
            else if (wtvi.IsImageFileExist)
            {
                System.Diagnostics.Process.Start("explorer.exe", $"\"{wtvi.FullPath}\"");
            }
        }

        /// <summary>
        /// 创建目录元文件。所谓目录元文件是为了 chm 提供的，创建 chm 目录时，会链接到目录而不是链接到目录下的文件条目。
        /// </summary>
        /// <param name="di">目录信息。</param>
        /// <param name="directoryMdFileFullName">目录对应元文件的路径。应该在该目录下并以“_目录名.md”为文件短名。</param>
        private static void CreateDirectoryMetaMdFile(DirectoryInfo di, string directoryMdFileFullName)
        {
            if (File.Exists(directoryMdFileFullName) == false)
            {
                using (StreamWriter sw = File.CreateText(directoryMdFileFullName))
                {
                    sw.Write($"\r\n%{FormatDocumentTitle(di.Name)}\r\n\r\n；{DateTime.Now.ToString()}");
                }
            }
        }

        /// <summary>
        /// 调用 Windows Explorer（资源管理器）打开当前工作区中选定的条目所在的目录。
        /// </summary>
        private void miOpenFolder_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("　　请先选中工作区目录中某一个条目！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            //如果是MD文件或图片，调用Explorer打开它所在的目录;
            //如果是目录本身，调用Explorer，直接打开该目录。
            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;

            if (wtvi.IsMarkdownFilePath || wtvi.IsImageFileExist)
            {
                FileInfo fi = new FileInfo(wtvi.FullPath);
                System.Diagnostics.Process.Start("explorer.exe", $"\"{fi.Directory.FullName}\"");
            }
            else if (wtvi.IsDirectoryExists)
            {
                System.Diagnostics.Process.Start("explorer.exe", $"\"{wtvi.FullPath}\"");
            }
        }

        /// <summary>
        /// 根据当前工作区中选择的条目创建一个同级目录。
        /// </summary>
        private void miNewBrotherDirectory_Click(object sender, RoutedEventArgs e)
        {
            if (tvWorkDirectory.SelectedItem == null)
            {
                LMessageBox.Show("　　请在工作区中选择一个目录或Markdown文件再执行此操作。", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            WorkspaceTreeViewItem wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            DirectoryInfo di = new DirectoryInfo(wtvi.FullPath);
            if (di.Name.EndsWith("~"))
            {
                LMessageBox.Show("　　以波形符结尾的目录是程序自动管理的资源目录，不允许在其下新建子目录。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            string parentDirectory = null;
            if (Directory.Exists(wtvi.FullPath))
            {
                parentDirectory = new DirectoryInfo(wtvi.FullPath).Parent.FullName;
            }
            else if (File.Exists(wtvi.FullPath))
            {
                if (wtvi.IsMarkdownFilePath)
                {
                    parentDirectory = new FileInfo(wtvi.FullPath).Directory.FullName;
                }
            }

            if (parentDirectory == null)
            {
                LMessageBox.Show("　　请在工作区中选择一个目录或Markdown文件再执行此操作。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var parentItem = wtvi.ParentWorkspaceTreeViewItem;

            if (wtvi.FullPath.ToLower() == Globals.PathOfWorkspace.ToLower())
            {
                LMessageBox.Show("　　不允许建立与工作区同级的子目录，新目录将会被包含在工作区目录中。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                parentDirectory = Globals.PathOfWorkspace;
                parentItem = wtvi;//工作区目录比较特殊
            }

            var newShortDirectoryName = InputBox.Show(Globals.AppName, "请输入新目录名：", "", true);
            if (string.IsNullOrEmpty(newShortDirectoryName))
            {
                LMessageBox.Show("　　目录名称不能为空！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            try
            {
                var newFullPath = (parentDirectory.EndsWith("\\") ? parentDirectory : (parentDirectory + "\\")) + newShortDirectoryName;
                if (Directory.Exists(newFullPath) == false)
                {
                    Directory.CreateDirectory(newFullPath);
                }
                var newItem = new WorkspaceTreeViewItem(newFullPath, Globals.MainWindow);
                if (parentItem != null)
                {
                    //parentItem.Items.Add(newItem);

                    List<WorkspaceTreeViewItem> tmpItems = new List<WorkspaceTreeViewItem>();
                    int baseIndex = 0;
                    foreach (var item in parentItem.Items)
                    {
                        var wi = item as WorkspaceTreeViewItem;
                        if (wi != null)
                        {
                            if (wi.ShortName.EndsWith("~"))//波型符结尾的总是资源文件夹。
                            {
                                baseIndex++;
                                continue;
                            }
                            tmpItems.Add(wi);
                        }
                    }
                    tmpItems.Add(newItem);
                    tmpItems.Sort(new WorkspaceTreeViewItemCompare());
                    var index = tmpItems.IndexOf(newItem) + baseIndex;
                    parentItem.Items.Insert(index, newItem);

                }
                newItem.IsSelected = true;
            }
            catch (Exception ex)
            {
                LMessageBox.Show("　　创建目录失败！错误消息：\r\n" + ex.Message + "\r\n" + ex.StackTrace,
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Error);

            }
        }

        /// <summary>
        /// 查找任务列表。
        /// </summary>
        /// <param name="searchHeader">[-]表示未开始；[%]表示正在进行；[+]表示已完成；![+]表示未完成；[#]表示已废弃。</param>
        public void FindTaskList(string searchHeader, TreeView treeView)
        {
            if (treeView == null) treeView = tvTaskList;

            treeView.Items.Clear();
            FindTaskList((cmbSearchArea.SelectedItem as ComboBoxItem).Tag.ToString(), searchHeader, treeView);

            if (treeView.Items.Count <= 0)
            {
                treeView.Items.Add(new TreeViewItem() { Header = "<没找到任务列表...>", });
            }

            cmbFindText.Items.Add(cmbFindText.Text);

            if (rightToolBar.SelectedIndex != 3)
            {
                rightToolBar.SelectedIndex = 3;
            }

            if (cdRightToolsArea.ActualWidth < 140)
            {
                cdLeftToolsArea.Width = new GridLength(2, GridUnitType.Star);
            }
        }

        /// <summary>
        /// 在磁盘文档或当前正在编辑的文档之间查找指定文本内容。
        /// </summary>
        /// <param name="searchArea">搜索的文档的范围（当前文档、打开的文档、全工作区）。</param>
        /// <param name="searchHeader">查找的任务项目的类型（[-]，未开始；[%]正在进行；[+]已完成；[#]已废弃。</param>
        private void FindTaskList(string searchArea, string searchHeader, TreeView treeView)
        {
            if (treeView == null) treeView = tvTaskList;

            treeView.Items.Clear();
            switch (searchArea)
            {
                case "ActiveDocument":
                    {
                        FindTaskListInActiveDocument(searchHeader, treeView);
                        break;
                    }
                case "OpenedDocuments":
                    {
                        FindTaskListInOpenedDocuments(searchHeader, treeView);
                        break;
                    }
                case "AllFiles":
                    {
                        //这个需要用到递归
                        FindTaskListInAllFiles(Globals.PathOfWorkspace, searchHeader, treeView);
                        break;
                    }
            }
        }

        /// <summary>
        /// 在打开的所有文档中查找任务列表。
        /// </summary>
        /// <param name="searchHeader">用以指定要查找的任务列表的类型。
        /// 应传入下列值之一：“[-]”、“[%]”、“[+]”、“![+]”、“[#]”、“”</param>
        /// <param name="treeView">指定将查找结果显示在哪个树型框中。</param>
        private void FindTaskListInOpenedDocuments(string searchHeader, TreeView treeView)
        {
            foreach (var item in this.mainTabControl.Items)
            {
                var efi = item as MarkdownEditor;
                if (efi != null)
                {
                    FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(efi.FullFilePath, efi.ShortFileName);
                    var lines = efi.EditorBase.Document.Lines;
                    for (int i = 1; i < lines.Count; i++)//第01行是文件状态，不属于正常的任务列表，忽略。
                    {
                        var line = lines[i];
                        var lineText = efi.EditorBase.Document.GetText(line.Offset, line.Length);
                        if (string.IsNullOrEmpty(lineText)) continue;

                        var tmp = lineText.Replace(" ", "").Replace("\t", "").Replace("　", "").Replace("：", ":");
                        bool isTaskListItem = false;
                        Brush foreColor = null;
                        if (tmp.StartsWith("[-]"))
                        {
                            isTaskListItem = true;
                            foreColor = Brushes.Red;
                        }
                        else if (tmp.StartsWith("[%]"))
                        {
                            isTaskListItem = true;
                            foreColor = Brushes.Green;
                        }
                        else if (tmp.StartsWith("[+]"))
                        {
                            isTaskListItem = true;
                            foreColor = Brushes.Blue;
                        }
                        else if (tmp.StartsWith("[#]"))
                        {
                            isTaskListItem = true;
                            foreColor = Brushes.Brown;
                        }

                        if (isTaskListItem)
                        {
                            bool isSearchItem = false;
                            if (string.IsNullOrEmpty(searchHeader))
                            {
                                isSearchItem = true;
                            }
                            else if (tmp.StartsWith(searchHeader))
                            {
                                isSearchItem = true;
                            }
                            else
                            {
                                if (searchHeader == "![+]")
                                {
                                    if (tmp.StartsWith("[-]") || tmp.StartsWith("[%]") || tmp.StartsWith("[#]"))
                                    {
                                        isSearchItem = true;
                                    }
                                }
                            }

                            if (isSearchItem)
                            {
                                int preIndex, nextIndex;
                                preIndex = lineText.IndexOf('[') + 1;
                                nextIndex = lineText.IndexOf(']') - 1;
                                TextDecorationCollection textDecoration = null;
                                if (tmp.StartsWith("[#]") || tmp.StartsWith("[+]"))
                                {
                                    textDecoration = TextDecorations.Strikethrough;
                                }

                                FindLineTreeViewItem newItem;
                                if (preIndex >= 0 && nextIndex >= 0 && nextIndex >= preIndex)
                                {
                                    newItem = new FindLineTreeViewItem(efi.FullFilePath, efi.ShortFileName, line.LineNumber, preIndex, nextIndex, lineText, foreColor,
                                         FindLineTreeViewItem.ItemType.TaskListItem, textDecoration);
                                    fdi.Items.Add(newItem);
                                }
                                else
                                {
                                    newItem = new FindLineTreeViewItem(efi.FullFilePath, efi.ShortFileName, line.LineNumber, 0, nextIndex, lineText, foreColor,
                                         FindLineTreeViewItem.ItemType.TaskListItem, textDecoration);
                                    fdi.Items.Add(newItem);
                                }

                                for (int j = i + 1; j < lines.Count; j++)
                                {
                                    var line2 = lines[j];
                                    var line2Text = efi.EditorBase.Document.GetText(line2.Offset, line2.Length);
                                    if (CustomMarkdownSupport.IsTaskLine(line2Text)) break;//到下一个任务列表项就停止。

                                    if (CustomMarkdownSupport.IsDateLine(line2Text))
                                    {
                                        var leftIndex = line2Text.IndexOf("[");
                                        var rightIndex = line2Text.IndexOf("]");
                                        if (leftIndex >= 0 && rightIndex > leftIndex)
                                        {
                                            var newDateItem = new FindLineTreeViewItem(efi.FullFilePath, efi.ShortFileName,
                                                line2.LineNumber, leftIndex + 1, rightIndex - leftIndex - 1, line2Text, foreColor, FindLineTreeViewItem.ItemType.Normal);
                                            newItem.Items.Add(newDateItem);
                                        }
                                        else
                                        {
                                            var newDateItem = new FindLineTreeViewItem(efi.FullFilePath, efi.ShortFileName,
                                                line2.LineNumber, 0, 0, line2Text, foreColor, FindLineTreeViewItem.ItemType.Normal);
                                            newItem.Items.Add(newDateItem);
                                        }
                                    }
                                }

                                newItem.IsExpanded = true;
                            }
                        }
                    }

                    if (fdi.Items.Count > 0)
                    {
                        treeView.Items.Add(fdi);
                        fdi.IsExpanded = true;
                        (fdi.Items[0] as TreeViewItem).IsSelected = true;
                    }
                }
            }
        }

        /// <summary>
        /// 在当前活动编辑器中查找任务列表。
        /// </summary>
        /// <param name="searchHeader">用以指定要查找的任务列表的类型。
        /// 应传入下列值之一：“[-]”、“[%]”、“[+]”、“![+]”、“[#]”、“”</param>
        /// <param name="treeView">指定将查找结果显示在哪个树型框中。</param>
        private void FindTaskListInActiveDocument(string searchHeader, TreeView treeView)
        {
            var efi = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (efi != null)
            {
                FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(efi.FullFilePath, efi.ShortFileName);
                var lines = efi.EditorBase.Document.Lines;
                for (int i = 1; i < lines.Count; i++)//第01行是文件状态，不属于正常的任务列表，忽略。
                {
                    var line = lines[i];
                    var lineText = efi.EditorBase.Document.GetText(line.Offset, line.Length);
                    if (string.IsNullOrEmpty(lineText)) continue;

                    var tmp = lineText.Replace(" ", "").Replace("\t", "").Replace("　", "").Replace("：", ":");
                    bool isTaskListItem = false;
                    Brush foreground = null;
                    if (tmp.StartsWith("[-]"))
                    {
                        isTaskListItem = true;
                        foreground = Brushes.Red;
                    }
                    else if (tmp.StartsWith("[%]"))
                    {
                        isTaskListItem = true;
                        foreground = Brushes.Green;
                    }
                    else if (tmp.StartsWith("[+]"))
                    {
                        isTaskListItem = true;
                        foreground = Brushes.Blue;
                    }
                    else if (tmp.StartsWith("[#]"))
                    {
                        isTaskListItem = true;
                        foreground = Brushes.Brown;
                    }

                    if (isTaskListItem)
                    {
                        bool isSearchItem = false;
                        if (string.IsNullOrEmpty(searchHeader))
                        {
                            isSearchItem = true;
                        }
                        else if (tmp.StartsWith(searchHeader))
                        {
                            isSearchItem = true;
                        }
                        else
                        {
                            if (searchHeader == "![+]")
                            {
                                if (tmp.StartsWith("[-]") || tmp.StartsWith("[%]") || tmp.StartsWith("[#]"))
                                {
                                    isSearchItem = true;
                                }
                            }
                        }

                        if (isSearchItem)
                        {
                            int preIndex, nextIndex;
                            preIndex = lineText.IndexOf('[') + 1;
                            nextIndex = lineText.IndexOf(']') - 1;
                            TextDecorationCollection textDecoration = null;
                            if (tmp.StartsWith("[#]") || tmp.StartsWith("[+]"))
                            {
                                textDecoration = TextDecorations.Strikethrough;
                            }

                            FindLineTreeViewItem newItem;
                            if (preIndex >= 0 && nextIndex >= 0 && nextIndex >= preIndex)
                            {
                                newItem = new FindLineTreeViewItem(efi.FullFilePath, efi.ShortFileName, line.LineNumber, preIndex, nextIndex, lineText, foreground,
                                     FindLineTreeViewItem.ItemType.TaskListItem, textDecoration);
                                fdi.Items.Add(newItem);
                            }
                            else
                            {
                                newItem = new FindLineTreeViewItem(efi.FullFilePath, efi.ShortFileName, line.LineNumber, 0, nextIndex, lineText, foreground,
                                     FindLineTreeViewItem.ItemType.TaskListItem, textDecoration);
                                fdi.Items.Add(newItem);
                            }

                            for (int j = i + 1; j < lines.Count; j++)
                            {
                                var line2 = lines[j];
                                var line2Text = efi.EditorBase.Document.GetText(line2.Offset, line2.Length);
                                if (CustomMarkdownSupport.IsTaskLine(line2Text)) break;//到下一个任务列表项就停止。

                                if (CustomMarkdownSupport.IsDateLine(line2Text))
                                {
                                    var leftIndex = line2Text.IndexOf("[");
                                    var rightIndex = line2Text.IndexOf("]");
                                    if (leftIndex >= 0 && rightIndex > leftIndex)
                                    {
                                        var newDateItem = new FindLineTreeViewItem(efi.FullFilePath, efi.ShortFileName,
                                            line2.LineNumber, leftIndex + 1, rightIndex - leftIndex - 1, line2Text, foreground, FindLineTreeViewItem.ItemType.Normal);
                                        newItem.Items.Add(newDateItem);
                                    }
                                    else
                                    {
                                        var newDateItem = new FindLineTreeViewItem(efi.FullFilePath, efi.ShortFileName,
                                            line2.LineNumber, 0, 0, line2Text, foreground, FindLineTreeViewItem.ItemType.Normal);
                                        newItem.Items.Add(newDateItem);
                                    }
                                }
                            }

                            newItem.IsExpanded = true;
                        }
                    }
                }

                if (fdi.Items.Count > 0)
                {
                    treeView.Items.Add(fdi);
                    fdi.IsExpanded = true;
                    (fdi.Items[0] as TreeViewItem).IsSelected = true;
                }
            }
        }

        /// <summary>
        /// 根据指定的文件路径查找当前有没有哪个编辑器正在编辑这个文件。
        /// </summary>
        /// <param name="mdFileFullName">Markdown 文件的磁盘路径。</param>
        /// <returns>返回正在编辑指定 Markdown 文件的编辑器。</returns>
        private MarkdownEditor GetOpenedEditor(string mdFileFullName)
        {
            if (string.IsNullOrWhiteSpace(mdFileFullName)) return null;

            if (this.mainTabControl.Items.Count > 0)
            {
                foreach (var ue in this.mainTabControl.Items)
                {
                    var editor = ue as MarkdownEditor;
                    if (editor == null) continue;
                    if (editor.FullFilePath == null) continue;

                    if (editor.FullFilePath.ToLower() == mdFileFullName.ToLower()) return editor;
                }
            }

            return null;
        }

        /// <summary>
        /// 递归查找指定目录下的所有 Markdown 文件中定义的所有任务列表项，将将查找结果显示在指定的树型框中。
        /// </summary>
        /// <param name="directoryPath">要查找的目录</param>
        /// <param name="searchHeader">要查找的任务列表的类型（[-]为未开始；[%]为正在进行；[+]是已完成；[#]是已废弃。）</param>
        /// <param name="treeView">指定要将查找结果显示在哪个树型框中。</param>
        private void FindTaskListInAllFiles(string directoryPath, string searchHeader, TreeView treeView)
        {
            if (Directory.Exists(directoryPath) == false) return;
            var directory = new DirectoryInfo(directoryPath);

            //先处理当前目录下的文件
            var childFilesInfos = directory.GetFiles();

            foreach (var childFileInfo in childFilesInfos)
            {
                if (childFileInfo.FullName.ToLower().EndsWith(".md") == false) continue;

                string[] lines;

                //已打开的文件，按打开的情况算，没打开的，按磁盘文本查找。
                var fileEditor = GetOpenedEditor(childFileInfo.FullName);
                if (fileEditor != null)
                {
                    lines = fileEditor.EditorBase.Text.Replace("\r", "").Split(new char[] { '\n' });
                }
                else
                {
                    lines = File.ReadAllLines(childFileInfo.FullName);
                }

                FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(childFileInfo.FullName, childFileInfo.Name);

                var lineNum = 0;
                for (int i = 1; i < lines.Length; i++)//第01行是文件状态，不属于正常的任务列表，忽略。
                {
                    lineNum++;

                    var lineText = lines[i];

                    if (string.IsNullOrEmpty(lineText)) continue;

                    var tmp = lineText.Replace(" ", "").Replace("\t", "").Replace("　", "").Replace("：", ":");
                    bool isTaskListItem = false;
                    Brush foreColor = null;
                    if (tmp.StartsWith("[-]"))
                    {
                        isTaskListItem = true;
                        foreColor = Brushes.Red;
                    }
                    else if (tmp.StartsWith("[%]"))
                    {
                        isTaskListItem = true;
                        foreColor = Brushes.Green;
                    }
                    else if (tmp.StartsWith("[+]"))
                    {
                        isTaskListItem = true;
                        foreColor = Brushes.Blue;
                    }
                    else if (tmp.StartsWith("[#]"))
                    {
                        isTaskListItem = true;
                        foreColor = Brushes.Brown;
                    }

                    if (isTaskListItem)
                    {
                        bool isSearchItem = false;
                        if (string.IsNullOrEmpty(searchHeader))
                        {
                            isSearchItem = true;
                        }
                        else if (tmp.StartsWith(searchHeader))
                        {
                            isSearchItem = true;
                        }
                        else
                        {
                            if (searchHeader == "![+]")
                            {
                                if (tmp.StartsWith("[-]") || tmp.StartsWith("[%]") || tmp.StartsWith("[#]"))
                                {
                                    isSearchItem = true;
                                }
                            }
                        }

                        if (isSearchItem)
                        {
                            int preIndex, nextIndex;
                            preIndex = lineText.IndexOf('[') + 1;
                            nextIndex = lineText.IndexOf(']') - 1;
                            TextDecorationCollection textDecoration = null;
                            if (tmp.StartsWith("[#]") || tmp.StartsWith("[+]"))
                            {
                                textDecoration = TextDecorations.Strikethrough;
                            }

                            FindLineTreeViewItem newItem;
                            if (preIndex >= 0 && nextIndex >= 0 && nextIndex >= preIndex)
                            {
                                newItem = new FindLineTreeViewItem(childFileInfo.FullName, childFileInfo.Name, lineNum, preIndex, nextIndex, lineText, foreColor,
                                     FindLineTreeViewItem.ItemType.TaskListItem, textDecoration);
                                fdi.Items.Add(newItem);
                            }
                            else
                            {
                                newItem = new FindLineTreeViewItem(childFileInfo.FullName, childFileInfo.Name, lineNum, 0, nextIndex, lineText, foreColor,
                                     FindLineTreeViewItem.ItemType.TaskListItem, textDecoration);
                                fdi.Items.Add(newItem);
                            }

                            for (int j = i + 1; j < lines.Length; j++)
                            {
                                var line2Text = lines[j];
                                if (CustomMarkdownSupport.IsTaskLine(line2Text)) break;//到下一个任务列表项就停止。

                                if (CustomMarkdownSupport.IsDateLine(line2Text))
                                {
                                    var leftIndex = line2Text.IndexOf("[");
                                    var rightIndex = line2Text.IndexOf("]");
                                    if (leftIndex >= 0 && rightIndex > leftIndex)
                                    {
                                        var newDateItem = new FindLineTreeViewItem(childFileInfo.FullName, childFileInfo.Name,
                                            j + 1, leftIndex + 1, rightIndex - leftIndex - 1, line2Text, foreColor, FindLineTreeViewItem.ItemType.Normal);
                                        newItem.Items.Add(newDateItem);
                                    }
                                    else
                                    {
                                        var newDateItem = new FindLineTreeViewItem(childFileInfo.FullName, childFileInfo.Name,
                                            j + 1, 0, 0, line2Text, foreColor, FindLineTreeViewItem.ItemType.Normal);
                                        newItem.Items.Add(newDateItem);
                                    }
                                }
                            }

                            newItem.IsExpanded = true;
                        }
                    }
                }

                if (fdi.Items.Count > 0)
                {
                    treeView.Items.Add(fdi);
                    fdi.IsExpanded = true;
                }
            }

            //再递归处理子目录下的文件
            var childDirectories = directory.GetDirectories();
            foreach (var childDirectory in childDirectories)
            {
                FindTaskListInAllFiles(childDirectory.FullName, searchHeader, treeView);
            }
        }

        /// <summary>
        /// 按次序切换活动文档当前选中行中任务列表或时间标记的“任务完成状态”标记。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void miSwitchTaskListItemOrDateTimeLineState_Click(object sender, RoutedEventArgs e)
        {
            if (this.mainTabControl.SelectedItem == null) return;

            var editor = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (editor == null) return;

            editor.EditorBase.SwitchTaskListItemOrDateTimeLineState(false, false);
        }

        /// <summary>
        /// 在当前活动编辑器当前选中行前面添加“无序列表”标记。
        /// </summary>
        private void miSetAsListItem_Click(object sender, RoutedEventArgs e)
        {
            if (this.mainTabControl.SelectedItem == null) return;

            var editor = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (editor == null) return;

            editor.EditorBase.SwitchListMark(true);
        }

        /// <summary>
        /// 删除当前活动编辑器当前选中行前面的“无序列表”标记。
        /// </summary>
        private void miDeleteListItemMark_Click(object sender, RoutedEventArgs e)
        {
            var editor = this.ActivedEditor;
            if (editor == null) return;

            editor.EditorBase.SwitchListMark(false);
        }

        /// <summary>
        /// 将当前选定的“历史工作区目录”中记录的目录设置为当前工作区目录。
        /// </summary>
        private void miSetAsCurrentWorkspace_Click(object sender, RoutedEventArgs e)
        {
            if (lbxHistoryWorkspaces.SelectedItem == null)
            {
                LMessageBox.Show("　　请先选定一个历史工作区记录！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var item = lbxHistoryWorkspaces.SelectedItem as RecentDirectoryListBoxItem;
            if (item == null) return;

            if (Globals.PathOfWorkspace.ToLower() == item.DirectoryPath.ToLower())
            {
                return;
            }

            ChangeWorkspace(item.DirectoryPath);
        }

        #region 最小化到托盘图标

        /// <summary>
        /// 用于点击托盘图标时切换窗口状态。
        /// </summary>
        WindowState windowState;

        /// <summary>
        /// 托盘图标对象。
        /// </summary>
        System.Windows.Forms.NotifyIcon notifyIcon;

        /// <summary>
        /// 创建 Windows 系统托盘图标。
        /// </summary>
        private void CreateNoticeIcon()
        {
            this.notifyIcon = new System.Windows.Forms.NotifyIcon();
            this.notifyIcon.BalloonTipText = Globals.AppName; //设置程序启动时显示的文本
            this.notifyIcon.Text = Globals.AppName;//最小化到托盘时，鼠标点击时显示的文本
            this.notifyIcon.Icon = new System.Drawing.Icon("App.ico");//程序图标
            this.notifyIcon.Visible = true;
            notifyIcon.MouseDoubleClick += OnNotifyIconDoubleClick;
            notifyIcon.MouseClick += NotifyIcon_MouseClick;
            //this.notifyIcon.ShowBalloonTip(1000);
        }

        /// <summary>
        /// 托盘图标的快捷菜单。
        /// </summary>
        private ContextMenu iconContextMenu = null;

        /// <summary>
        /// 点击托盘图标时弹出快捷菜单。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void NotifyIcon_MouseClick(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (iconContextMenu == null)
            {
                iconContextMenu = new ContextMenu();
                MenuItem miShowWindow = new MenuItem() { Header = "显示窗口(_S)" };
                miShowWindow.Click += MiShowWindow_Click;

                MenuItem miExit = new MenuItem() { Header = "退出(_X)" };
                miExit.Click += MiExit_Click;

                this.iconContextMenu.Items.Add(miShowWindow);
                this.iconContextMenu.Items.Add(miExit);
            }

            iconContextMenu.IsOpen = true;
        }

        /// <summary>
        /// 点击托盘图标快捷菜单中的“退出程序”，关闭程序。
        /// </summary>
        private void MiExit_Click(object sender, RoutedEventArgs e)
        {
            //App.Current.Shutdown();//注意，这能使用这个
            this.isForceExit = true;//强行忽略这个字段的值。
            this.Close();
        }

        /// <summary>
        /// 点击托盘图标中的“显示窗口”，使程序主窗口显示出来。
        /// </summary>
        private void MiShowWindow_Click(object sender, RoutedEventArgs e)
        {
            this.Show();
            WindowState = windowState;
        }

        /// <summary>
        /// 双击托盘图标显示程序主窗口。
        /// </summary>
        private void OnNotifyIconDoubleClick(object sender, EventArgs e)
        {
            this.Show();
            WindowState = windowState;
        }

        private bool isCloseToIcon = false;
        /// <summary>
        /// 关闭到托盘图标。
        /// </summary>
        public bool IsCloseToIcon
        {
            get
            {
                return isCloseToIcon;
            }
        }

        /// <summary>
        /// 是否在编译工作区时忽略被加密的文件，否则每次都会要求输入密码。
        /// </summary>
        public bool IgnoreEncryptedFiles { get; private set; }

        /// <summary>
        /// 忽略isCloseToIcon的值，强制退出。
        /// </summary>
        private bool isForceExit = false;

        /// <summary>
        /// 开关“CloseToIcon”属性。此属性决定点击窗口右上角“关闭”按钮时是退出程序还是隐藏到托盘图标。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void miCloseToIcon_Click(object sender, RoutedEventArgs e)
        {
            isCloseToIcon = miCloseToIcon.IsChecked = !miCloseToIcon.IsChecked;

            this.ConfigManager.Set("CloseToIcon", isCloseToIcon.ToString());
        }
        #endregion

        /// <summary>
        /// 在工作区管理器中选定的目录位置查找该目录下所有 Markdown 文件中定义的锚。
        /// </summary>
        private void miSearchAnchorsInDirectory_Click(object sender, RoutedEventArgs e)
        {
            var wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;

            if (wtvi == null)
            {
                LMessageBox.Show("　　请先在工作区管理器中选择一个目录作为查找目标！", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            if (wtvi.IsDirectoryExists == false)
            {
                LMessageBox.Show("　　请选择目录而不是其它项目！", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            if (wtvi.FullPath.EndsWith("~") || wtvi.FullPath.EndsWith("~\\"))
            {
                LMessageBox.Show("　　以波型符结尾的目录是资源目录，不允许执行此操作！", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            //锚的定义方式：
            //[锚名](@锚ID)
            //锚名可以省略，但锚ID不能省略。
            //编译后，会变成这样：
            //<a id="锚ID">锚名</a>
            FindTextInAllFiles(wtvi.FullPath, @"\[.*\]\(@.*\)", true, tvFindAndReplace);

            if (rightToolBar.SelectedIndex != 1)
            {
                rightToolBar.SelectedIndex = 1;
            }

            if (cdRightToolsArea.ActualWidth < 140)
            {
                cdLeftToolsArea.Width = new GridLength(2, GridUnitType.Star);
            }

        }

        /// <summary>
        /// 根据工作区管理器中指定的目录，查找该目录下所有 Markdown 文件中定义的所有指定类型任务列表项。
        /// </summary>
        /// <param name="headerKey">要查找什么类型的任务列表，需要传入“[-]”、“[+]”、“![+]”、“[#]”、“”、“[%]”这几种之一。</param>
        /// <param name="treeView">指定要将查找结果显示在哪个树型框中。</param>
        private void FindTaskListInDirectory(string headerKey, TreeView treeView)
        {
            if (treeView == null) treeView = tvTaskList;

            treeView.Items.Clear();

            var wtvi = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;

            if (wtvi == null)
            {
                LMessageBox.Show("　　请先在工作区管理器中选择一个目录作为查找目标！", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            if (wtvi.IsDirectoryExists == false)
            {
                LMessageBox.Show("　　请选择目录而不是其它项目！", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            if (wtvi.FullPath.EndsWith("~") || wtvi.FullPath.EndsWith("~\\"))
            {
                LMessageBox.Show("　　以波型符结尾的目录是资源目录，不允许执行此操作！", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            FindTaskListInAllFiles(wtvi.FullPath, headerKey, treeView);

            if (tvTaskList.Items.Count <= 0)
            {
                tvTaskList.Items.Add("<没找到结果...>");
            }

            if (rightToolBar.SelectedIndex != 3)
            {
                rightToolBar.SelectedIndex = 3;
            }

            if (cdRightToolsArea.ActualWidth < 140)
            {
                cdLeftToolsArea.Width = new GridLength(3, GridUnitType.Star);
            }
        }

        /// <summary>
        /// 根据工作区管理器中指定的目录，查找该目录下所有 Markdown 文件中定义的所有“未开始”的任务列表项。
        /// </summary>
        private void miSearchUnStartTaskListItemInDirectory_Click(object sender, RoutedEventArgs e)
        {
            FindTaskListInDirectory("[-]", tvTaskList);
        }

        /// <summary>
        /// 根据工作区管理器中指定的目录，查找该目录下所有 Markdown 文件中定义的所有“未完成”的任务列表项。
        /// </summary>
        private void miSearchUnFinishedTaskListItemInDirectory_Click(object sender, RoutedEventArgs e)
        {
            FindTaskListInDirectory("![+]", tvTaskList);
        }

        /// <summary>
        /// 根据工作区管理器中指定的目录，查找该目录下所有 Markdown 文件中定义的所有“正在进行”的任务列表项。
        /// </summary>
        private void miSearchProcessingTaskListItemInDirectory_Click(object sender, RoutedEventArgs e)
        {
            FindTaskListInDirectory("[%]", tvTaskList);
        }

        /// <summary>
        /// 根据工作区管理器中指定的目录，查找该目录下所有 Markdown 文件中定义的所有“废弃”的任务列表项。
        /// </summary>
        private void miSearchAbortedTaskListItemInDirectory_Click(object sender, RoutedEventArgs e)
        {
            FindTaskListInDirectory("[#]", tvTaskList);
        }

        /// <summary>
        /// 根据工作区管理器中指定的目录，查找该目录下所有 Markdown 文件中定义的所有“已完成”的任务列表项。
        /// </summary>
        private void miSearchFinishedTaskListItemInDirectory_Click(object sender, RoutedEventArgs e)
        {
            FindTaskListInDirectory("[+]", tvTaskList);
        }

        /// <summary>
        /// 根据工作区管理器中指定的目录，查找该目录下所有 Markdown 文件中定义的所有任务列表项。
        /// </summary>
        private void miSearchAllTaskListItemInDirectory_Click(object sender, RoutedEventArgs e)
        {
            FindTaskListInDirectory("", tvTaskList);
        }

        /// <summary>
        /// 根据主界面“查找范围”框的限制，查找所有“未开始”的任务列表项。
        /// </summary>
        private void miSearchUnStartTaskListItem_Click(object sender, RoutedEventArgs e)
        {
            FindTaskList("[-]", tvTaskList);
        }

        /// <summary>
        /// 根据主界面“查找范围”框的限制，查找所有“正在进行”的任务列表项。
        /// </summary>
        private void miSearchProcessingTaskListItem_Click(object sender, RoutedEventArgs e)
        {
            FindTaskList("[%]", tvTaskList);
        }

        /// <summary>
        /// 根据主界面“查找范围”框的限制，查找所有“废弃”的任务列表项。
        /// </summary>
        private void miSearchAbortedTaskListItem_Click(object sender, RoutedEventArgs e)
        {
            FindTaskList("[#]", tvTaskList);
        }

        /// <summary>
        /// 根据主界面“查找范围”框的限制，查找所有“已完成”的任务列表项。
        /// </summary>
        private void miSearchFinishedTaskListItem_Click(object sender, RoutedEventArgs e)
        {
            FindTaskList("[+]", tvTaskList);
        }

        /// <summary>
        /// 根据主界面“查找范围”框的限制，查找所有任务列表项。
        /// </summary>
        private void miSearchAllTaskListItem_Click(object sender, RoutedEventArgs e)
        {
            FindTaskList("", tvTaskList);
        }

        /// <summary>
        /// 根据主界面“查找范围”框的限制，查找所有任务列表项。
        /// </summary>
        private void btnFindTaskListItem_Click(object sender, RoutedEventArgs e)
        {
            FindTaskList("", tvTaskList);
        }

        /// <summary>
        /// 根据主界面“查找范围”框的限制，查找所有“未完成”的任务列表项。
        /// </summary>
        private void miSearchUnFinishedTaskListItem_Click(object sender, RoutedEventArgs e)
        {
            FindTaskList("![+]", tvTaskList);
        }

        /// <summary>
        /// 打开“查找/替换”面板，准备在当前文档中查找当前活动编辑器中选定的文本。
        /// 打开面板后立即查找第一个符合条件的文本片段。
        /// 如果当前没有打开任何文档，就准备在全工作区中查找。
        /// </summary>
        private void miFindInActiveDocument_Click(object sender, RoutedEventArgs e)
        {
            if (rdFindAndReplace.ActualHeight <= 40)
            {
                rdFindAndReplace.Height = new GridLength(140, GridUnitType.Auto);
                cmbFindText.UpdateLayout();
            }

            var editor = ActivedEditor;
            if (editor != null)
            {
                cmbFindText.Text = editor.EditorBase.SelectedText;
            }
            else
            {
                cmbSearchArea.SelectedIndex = 2;//没有打开任何文档，查找全工作区。
                return;
            }

            cmbSearchArea.SelectedIndex = 0;

            cmbFindText.Focus();
            if (cmbFindText.Text.Length > 0)
            {
                btnFind_Click(sender, e);
            }

            //独立窗口效果不好，整合到主界面上。
            //var activeEditor = this.mainTabControl.SelectedItem as MarkdownEditor;
            //if (activeEditor != null)
            //{
            //    FindReplaceDialog.ShowForFind(activeEditor.EditorBase, this);
            //}
        }

        /// <summary>
        /// 打开“查找/替换”面板，准备在当前文档中查找并替换文本。
        /// 如果当前没有打开任何文档，就准备在全工作区中查找并替换。
        /// </summary>
        private void miFindAndReplaceInActiveDocument_Click(object sender, RoutedEventArgs e)
        {
            //打开查找替换面板
            if (gdFindAndReplace.Visibility != Visibility.Visible)
            {
                gdFindAndReplace.Visibility = Visibility.Visible;
                cmbFindText.UpdateLayout();
            }
            var editor = ActivedEditor;
            if (editor != null)
            {
                cmbFindText.Text = editor.EditorBase.SelectedText;
            }
            else
            {
                cmbSearchArea.SelectedIndex = 2;//没有打开任何文档，查找全工作区。
                return;
            }

            cmbSearchArea.SelectedIndex = 0;

            if (cmbFindText.Text.Length > 0)
            {
                cmbReplaceTextInputBox.Focus();
            }

            //var activeEditor = this.mainTabControl.SelectedItem as MarkdownEditor;
            //if (activeEditor != null)
            //{
            //    FindReplaceDialog.ShowForReplace(activeEditor.EditorBase, this);
            //}
        }

        /// <summary>
        /// 在当前活动编辑器的当前行插入一个时间标签文本（形如： [2016-05-13 10:00] xxx)。
        /// 如果当前行本身就是时间标签，则将其时间更改为当前时间。
        /// </summary>
        private void miInsertDate_Click(object sender, RoutedEventArgs e)
        {
            InsertDateText();
        }

        /// <summary>
        /// 从指定的时间生成格式化的日期文本。格式化后的时间类似：2016-05-13。
        /// 这样做的目的是保证每个时间标签的长度一致。
        /// </summary>
        /// <param name="datetime"></param>
        /// <returns></returns>
        public static string FormatDateText(DateTime datetime)
        {
            var srcText = datetime.Date.ToShortDateString();
            var spans = srcText.Split(new char[] { '-', '.', ',', '，', '。', '－', '、', '．', '/' }, StringSplitOptions.RemoveEmptyEntries);

            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < spans.Length; i++)
            {
                var span = spans[i];
                if (span.Length == 1)
                {
                    span = '0' + span;
                }
                sb.Append(span + "-");
            }

            var result = sb.ToString();
            if (result.EndsWith("-")) return result.Substring(0, result.Length - 1);

            return result;
        }

        /// <summary>
        /// 从指定的时间生成格式化的日期、时间文本。格式化后的时间类似：2016-05-13 10:01。
        /// 这样做的目的是保证每个时间标签的长度一致。
        /// </summary>
        public static string FormatDateTimeText(DateTime datetime)
        {
            var srcText = datetime.Date.ToShortDateString();
            var spans = srcText.Split(new char[] { '-', '.', ',', '，', '。', '－', '、', '．', '/' }, StringSplitOptions.RemoveEmptyEntries);

            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < spans.Length; i++)
            {
                var span = spans[i];
                if (span.Length == 1)
                {
                    span = '0' + span;
                }
                sb.Append(span + "-");
            }

            var result = sb.ToString();
            if (result.EndsWith("-")) result = result.Substring(0, result.Length - 1);

            var timeText = datetime.ToShortTimeString();
            StringBuilder sb2 = new StringBuilder();
            var spans2 = timeText.Split(new char[] { ':' });
            for (int i = 0; i < spans2.Length; i++)
            {
                var span = spans2[i];
                if (span.Length == 1)
                {
                    span = '0' + span;
                }
                sb2.Append(span + ":");
            }
            var result2 = sb2.ToString();
            result += " " + result2.Substring(0, result2.Length - 1);

            return result;
        }

        /// <summary>
        /// 在当前活动编辑器的当前行插入一个时间标签文本（形如： [2016-05-13 10:00] xxx)。
        /// 如果当前行本身就是时间标签，则将其时间更改为当前时间。
        /// </summary>
        private void InsertDateText()
        {
            var activeEditor = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (activeEditor == null) return;

            var now = DateTime.Now;
            var header = "";
            if (activeEditor.EditorBase.Document.GetLineByOffset(activeEditor.EditorBase.SelectionStart).Offset == activeEditor.EditorBase.SelectionStart)
            {
                header = " ";//自动格式化一下
            }

            var line = activeEditor.EditorBase.Document.GetLineByOffset(activeEditor.EditorBase.SelectionStart);
            var lineText = activeEditor.EditorBase.Document.GetText(line.Offset, line.Length);
            if (CustomMarkdownSupport.IsTaskLine(lineText))
            {
                //在最后插入一个新行（2016年5月26日废弃此方案）
                #region 废弃，这个方案不好——如果任务列表中项目过多，可能导致难以查看
                //DocumentLine lastDateLine = null;
                //for (int i = line.LineNumber + 1; i <= activeEditor.EditorBase.Document.LineCount; i++)//LineNumber从１开始。
                //{
                //    var l = activeEditor.EditorBase.Document.GetLineByNumber(i);
                //    var lt = activeEditor.EditorBase.Document.GetText(l.Offset, l.Length);

                //    if (CustomMarkdownSupport.IsDateLine(lt))
                //    {
                //        lastDateLine = l;
                //    }
                //    else if (CustomMarkdownSupport.IsTaskLine(lt) || lt.StartsWith("#"))
                //    {
                //        if (i - 2 > line.LineNumber)
                //        {
                //            lastDateLine = activeEditor.EditorBase.Document.GetLineByNumber(i - 2);
                //        }
                //        else if (i - 1 > line.LineNumber)
                //        {
                //            lastDateLine = activeEditor.EditorBase.Document.GetLineByNumber(i - 2);
                //        }
                //        break;
                //    }
                //}

                //if (lastDateLine == null) lastDateLine = line;//如果找不到，就直接在当前行后面添加。 
                #endregion

                var markText = CustomMarkdownSupport.GetHeaderOfTaskListItem(lineText);
                if (markText.Contains("-")) markText = "[-]";
                else if (markText.Contains("%")) markText = "[%]";
                else if (markText.Contains("#")) markText = "[#]";
                else if (markText.Contains("+")) markText = "[+]";
                else markText = "";

                header = " ";

                var newDateLine = $"{header}[{FormatDateText(now)} {now.ToShortTimeString()}]{markText} ";
                activeEditor.EditorBase.Document.Insert(/*lastDateLine*/line.EndOffset, $"\r\n\r\n{newDateLine}");
                var destSel = /*lastDateLine*/line.EndOffset + 4 + newDateLine.Length;
                if (destSel < activeEditor.EditorBase.Document.TextLength)
                {
                    activeEditor.EditorBase.Select(destSel, 0);
                    activeEditor.EditorBase.ScrollToLine(/*lastDateLine*/line.LineNumber + 2);
                }
            }
            else if (CustomMarkdownSupport.IsDateLine(lineText))
            {
                //修改当前行的时间
                var indexStart = lineText.IndexOf("[");
                var indexEnd = lineText.IndexOf("]");
                if (indexStart >= 0 && indexEnd >= 0)
                {
                    //修改当前时间
                    var newLineText = lineText.Substring(0, indexStart + 1) + FormatDateText(DateTime.Now) +
                       $" {now.ToShortTimeString()}" + lineText.Substring(indexEnd);
                    activeEditor.EditorBase.Document.Replace(line.Offset, line.Length, newLineText);
                }
                else
                {
                    //在当前位置插入时间
                    activeEditor.EditorBase.SelectedText = $"{header}[{FormatDateText(now)} {now.ToShortTimeString()}] ";
                    var destSel = activeEditor.EditorBase.SelectionStart + activeEditor.EditorBase.SelectionLength;
                    if (destSel < activeEditor.EditorBase.Document.TextLength)
                    {
                        activeEditor.EditorBase.Select(destSel, 0);
                    }
                }
            }
            else
            {
                //在当前位置插入时间

                activeEditor.EditorBase.SelectedText = $"{header}[{FormatDateText(now)} {now.ToShortTimeString()}] ";
                var destSel = activeEditor.EditorBase.SelectionStart + activeEditor.EditorBase.SelectionLength;
                if (destSel < activeEditor.EditorBase.Document.TextLength)
                {
                    activeEditor.EditorBase.Select(destSel, 0);
                }
            }
        }

        /// <summary>
        /// 开关“IgnoreEncryptedFile”选项。此选项决定在编译工作区时是否跳过被加密的文件。
        /// 如此选项被关闭，则编译被加密的每个文件时都有可能要求用户输入一次密码。
        /// </summary>
        private void miIgnoreEncryptedFile_Click(object sender, RoutedEventArgs e)
        {
            this.IgnoreEncryptedFiles =
            miIgnoreEncryptedFile.IsChecked = !miIgnoreEncryptedFile.IsChecked;
            this.ConfigManager.Set("IgnoreEncryptedFiles", miIgnoreEncryptedFile.IsChecked.ToString());
        }

        /// <summary>
        /// 开关“HideExamAnswer”选项。此选项用于在编译 Html 时决定编译后的页面中试题的答案是否直接显示。
        /// 若此选项开启，则不显示答案；若关闭此选项，则编译后的 Html 中直接显示答案。
        /// </summary>
        private void miHideExamAnswer_Click(object sender, RoutedEventArgs e)
        {
            this.hideExamAnswer =
            miHideExamAnswer.IsChecked = !miHideExamAnswer.IsChecked;
            this.ConfigManager.Set("HideExamAnswer", miHideExamAnswer.IsChecked.ToString());
        }

        /// <summary>
        /// 关闭“查找/替换”面板。
        /// </summary>
        private void btnCloseFindAndReplacePanel_Click(object sender, RoutedEventArgs e)
        {
            rdFindAndReplace.Height = new GridLength(0);
            if (ActivedEditor != null) FocusActiveEditor();
        }

        /// <summary>
        /// 激活当前活动编辑器（使之获得焦点）。
        /// </summary>
        public void FocusActiveEditor()
        {
            //重置焦点。
            this.mainTabControl.Focus();

            var efi = ActivedEditor;
            if (efi == null) return;

            efi.EditorBase.UpdateLayout();
            var destSel = efi.EditorBase.Text.Length;
            if (destSel < efi.EditorBase.Document.TextLength)
            {
                efi.EditorBase.Select(destSel, 0);
            }
            efi.UpdateLayout();
            efi.EditorBase.Focus();
        }

        /// <summary>
        /// 在当前活动编辑器中查找下一个符合条件的文本片段。
        /// </summary>
        private void btnFind_Click(object sender, RoutedEventArgs e)
        {
            Find();
        }

        /// <summary>
        /// 在当前活动编辑器中查找下一个符合条件的文本片段。
        /// </summary>
        public void Find()
        {
            if (!FindNext(cmbFindText.Text))
                SystemSounds.Beep.Play();

            cmbFindText.Items.Insert(0, cmbFindText.Text);
        }

        /// <summary>
        /// 根据“查找/替换”面板中配置的选项，生成正则表达式对象。
        /// </summary>
        /// <param name="textToFind">要查找的文本。</param>
        /// <param name="leftToRight">是否顺序查找（从前向后、从后向前）</param>
        /// <param name="forceRegex">是否强制按正则表达式查找。</param>
        /// <returns>返回一个正则对象。</returns>
        private Regex GetRegEx(string textToFind, bool leftToRight, bool forceRegex)
        {
            RegexOptions options = RegexOptions.None;
            if (cbSearchUp.IsChecked == true && !leftToRight)
                options |= RegexOptions.RightToLeft;
            if (cbCaseSensitive.IsChecked == false)
                options |= RegexOptions.IgnoreCase;

            options |= RegexOptions.Multiline;

            if (cbRegex.IsChecked == true || forceRegex)
            {
                return new Regex(textToFind, options);
            }
            else
            {
                string pattern = Regex.Escape(textToFind);
                if (cbWildcards.IsChecked == true)
                {
                    pattern = pattern.Replace("\\*", ".*").Replace("\\?", ".");
                }

                Regex convert = new Regex(@"\\.");
                pattern = convert.Replace(pattern, new MatchEvaluator(ConvertEscapeChars));

                if (cbWholeWord.IsChecked == true)
                    pattern = "\\b" + pattern + "\\b";
                return new Regex(pattern, options);
            }
        }

        /// <summary>
        /// 在当前活动编辑器中查找下一个符合条件的文本片段。
        /// </summary>
        private bool FindNext(string textToFind)
        {
            try
            {
                var activeEditor = ActivedEditor;
                if (activeEditor == null) return false;

                Regex regex = GetRegEx(textToFind, false, false);
                int start = regex.Options.HasFlag(RegexOptions.RightToLeft) ?
                activeEditor.EditorBase.SelectionStart : activeEditor.EditorBase.SelectionStart + activeEditor.EditorBase.SelectionLength;
                Match match = regex.Match(activeEditor.EditorBase.Text, start);

                if (!match.Success)  // start again from beginning or end
                {
                    if (regex.Options.HasFlag(RegexOptions.RightToLeft))
                        match = regex.Match(activeEditor.EditorBase.Text, activeEditor.EditorBase.Text.Length);
                    else
                        match = regex.Match(activeEditor.EditorBase.Text, 0);
                }

                if (match.Success)
                {
                    var destSel = match.Index;
                    if (destSel < activeEditor.EditorBase.Document.TextLength)
                    {
                        activeEditor.EditorBase.Select(destSel, match.Length);
                    }
                    TextLocation loc = activeEditor.EditorBase.Document.GetLocation(match.Index);
                    activeEditor.EditorBase.ScrollTo(loc.Line, loc.Column);
                }

                return match.Success;
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return false;
            }
        }

        /// <summary>
        /// 在当前活动编辑器中按指定条件顺序替换。
        /// </summary>
        private void btnReplace_Click(object sender, RoutedEventArgs e)
        {
            var activeEditor = ActivedEditor;
            if (activeEditor == null) return;

            try
            {
                Regex regex = GetRegEx(cmbFindText.Text, false, false);
                string input = activeEditor.EditorBase.Text.Substring(activeEditor.EditorBase.SelectionStart, activeEditor.EditorBase.SelectionLength);
                Match match = regex.Match(input);
                bool replaced = false;
                if (match.Success && match.Index == 0 && match.Length == input.Length)
                {
                    var newTextForReplace = cmbReplaceTextInputBox.Text;

                    Regex convert1 = new Regex(@"\\.");
                    newTextForReplace = convert1.Replace(newTextForReplace, new MatchEvaluator(ConvertEscapeChars));

                    Regex convert2 = new Regex(@"\\{1,2}x");
                    var matches = convert2.Matches(newTextForReplace);
                    for (int j = matches.Count - 1; j >= 0; j--)
                    {
                        var xMatch = matches[j];
                        if (xMatch.Value == @"\\x")
                        {
                            newTextForReplace = newTextForReplace.Substring(0, xMatch.Index) + @"\x" +
                                newTextForReplace.Substring(xMatch.Index + xMatch.Length);
                        }
                        else if (xMatch.Value == @"\x")
                        {
                            newTextForReplace = newTextForReplace.Substring(0, xMatch.Index) + match.Value   //match.Value，引用查找结果
                                + newTextForReplace.Substring(xMatch.Index + xMatch.Length);
                        }
                    }

                    activeEditor.EditorBase.Document.Replace(activeEditor.EditorBase.SelectionStart,
                    activeEditor.EditorBase.SelectionLength, newTextForReplace);
                    replaced = true;
                }

                if (!FindNext(cmbFindText.Text) && !replaced)
                    SystemSounds.Beep.Play();

                cmbFindText.Items.Insert(0, cmbFindText.Text);
                cmbReplaceTextInputBox.Items.Insert(0, cmbReplaceTextInputBox.Text);
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 在当前文档中按“查找”框中指定的关键词查找所有符合条件的结果，并全部替换为“替换”框中指定的文本。
        /// </summary>
        private void btnReplaceAll_Click(object sender, RoutedEventArgs e)
        {
            var activeEditor = ActivedEditor;
            if (activeEditor == null) return;

            var result = LMessageBox.Show("你确定把所有找到的 “" +
                                cmbFindText.Text + "” 替换成 “" + cmbReplaceTextInputBox.Text + "”吗？",
                                "Lunar Markdown Edit - 全部替换", MessageBoxButton.OKCancel, MessageBoxImage.Question);
            if (result == MessageBoxResult.OK)
            {
                try
                {
                    Regex regex = GetRegEx(cmbFindText.Text, true, false);
                    activeEditor.EditorBase.BeginChange();

                    var matchs = regex.Matches(activeEditor.EditorBase.Text);
                    int replacedCount = 0;
                    for (int i = matchs.Count - 1; i >= 0; i--)
                    {
                        var match = matchs[i];

                        if (cbReplaceOnlyInSelectionText.IsChecked == true)
                        {
                            if (match.Index + match.Length > activeEditor.EditorBase.SelectionStart + activeEditor.EditorBase.SelectionLength) continue;
                            if (match.Index < activeEditor.EditorBase.SelectionStart) break;//不需要再考虑index更小的情况了
                        }

                        var newTextForReplace = cmbReplaceTextInputBox.Text;

                        Regex convert = new Regex(@"\\.");
                        newTextForReplace = convert.Replace(newTextForReplace, new MatchEvaluator(ConvertEscapeChars));

                        Regex convert2 = new Regex(@"\\{1,2}x");
                        var matches = convert2.Matches(newTextForReplace);
                        for (int j = matches.Count - 1; j >= 0; j--)
                        {
                            var xMatch = matches[j];
                            if (xMatch.Value == @"\\x")
                            {
                                newTextForReplace = newTextForReplace.Substring(0, xMatch.Index) + @"\x" +
                                    newTextForReplace.Substring(xMatch.Index + xMatch.Length);
                            }
                            else if (xMatch.Value == @"\x")
                            {
                                newTextForReplace = newTextForReplace.Substring(0, xMatch.Index) + match.Value   //match.Value，引用查找结果
                                    + newTextForReplace.Substring(xMatch.Index + xMatch.Length);
                            }
                        }

                        activeEditor.EditorBase.Document.Replace(match.Index, match.Length, newTextForReplace);
                        replacedCount++;
                    }
                    activeEditor.EditorBase.EndChange();

                    cmbFindText.Items.Insert(0, cmbFindText.Text);
                    cmbReplaceTextInputBox.Items.Insert(0, cmbReplaceTextInputBox.Text);

                    if (cbReplaceOnlyInSelectionText.IsChecked == true)
                    {
                        LMessageBox.Show($"在当前文档的选定文本中替换了【{replacedCount}】处文本。", Globals.AppName,
                              MessageBoxButton.OK, MessageBoxImage.Information);
                    }
                    else
                    {
                        LMessageBox.Show($"在当前文档中替换了【{replacedCount}】处文本。", Globals.AppName,
                              MessageBoxButton.OK, MessageBoxImage.Information);
                    }
                }
                catch (Exception ex)
                {
                    LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                }
            }
        }

        /// <summary>
        /// 替换框中“\r”会被当作回车符、“\n”会被当作换行符、“\t”会被解释为Tab符、“\\”会被解释为单个“\”。
        /// 所以想要替换“\r”本身时，需要转义。
        /// </summary>
        /// <param name="m">正则表达式匹配项。</param>
        /// <returns>转义后的文本。</returns>
        static string ConvertEscapeChars(Match m)
        {
            string x = m.ToString();
            switch (m.Value)
            {
                case "\\\\": return "\\";
                case "\\r": return "\r";
                case "\\n": return "\n";
                case "\\t": return "\t";
                default: return m.Value;
            }
        }

        /// <summary>
        /// 刷新“查找/替换”面板上各部件的“可用/禁用”状态。避免用户误操作。
        /// </summary>
        private void cmbSearchArea_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            RefreshFindButtonsStatus();
        }

        /// <summary>
        /// 刷新“查找/替换”面板上各部件的“可用/禁用”状态。避免用户误操作。
        /// </summary>
        private void RefreshFindButtonsStatus()
        {
            if (btnFind == null) return;
            if (btnReplace == null) return;
            if (btnReplaceAll == null) return;
            if (cmbReplaceTextInputBox == null) return;
            if (cbCaseSensitive == null) return;
            if (cbWholeWord == null) return;
            if (cbRegex == null) return;
            if (cbWildcards == null) return;
            if (cbSearchUp == null) return;
            if (cbReplaceOnlyInSelectionText == null) return;

            switch (cmbSearchArea.SelectedIndex)
            {
                case 0:
                    {
                        btnFind.IsEnabled =
                            btnReplace.IsEnabled =
                            btnReplaceAll.IsEnabled =
                            cmbReplaceTextInputBox.IsEnabled =
                            cbSearchUp.IsEnabled =
                            cbReplaceOnlyInSelectionText.IsEnabled = true;

                        cbSearchUp.Foreground = Brushes.Black;
                        cbReplaceOnlyInSelectionText.Foreground = Brushes.Brown;

                        //cbCaseSensitive.IsEnabled =
                        //cbRegex.IsEnabled =
                        //cbSearchUp.IsEnabled =
                        //cbWholeWord.IsEnabled =
                        //cbWildcards.IsEnabled = true;
                        break;
                    }
                default:
                    {
                        btnFind.IsEnabled =
                            btnReplace.IsEnabled =
                            btnReplaceAll.IsEnabled =
                            cmbReplaceTextInputBox.IsEnabled =
                            cbSearchUp.IsEnabled =
                            cbReplaceOnlyInSelectionText.IsEnabled = false;

                        cbReplaceOnlyInSelectionText.IsChecked = false;

                        cbSearchUp.Foreground = Brushes.Gray;
                        cbReplaceOnlyInSelectionText.Foreground = Brushes.Gray;

                        //cbCaseSensitive.IsEnabled =
                        //cbRegex.IsEnabled =
                        //cbSearchUp.IsEnabled =
                        //cbWholeWord.IsEnabled =
                        //cbWildcards.IsEnabled = false;
                        break;
                    }
            }
        }

        /// <summary>
        /// 主界面“查找任务列表”按钮右侧边附加的小三角按钮，点击会弹出一个快捷菜单，可以用来查找指定类型的任务列表。
        /// 这两个按钮看起来就是个 SplitButton。此方法用以实现类似 SplitButton 的效果。
        /// </summary>
        private void btnFindTaskListItems_AppendArrow_Initialized(object sender, EventArgs e)
        {
            //SplitterButton的弹出菜单要处理一下，左键单显示，右键不显示
            splitMenu.PlacementTarget = btnFindTaskListItems_AppendArrow;
            btnFindTaskListItems_AppendArrow.ContextMenu = null;
        }

        /// <summary>
        /// 主界面“查找标题”按钮右侧边附加的小三角按钮，点击会弹出一个快捷菜单，可以用来查找指定级别的标题。
        /// 这两个按钮看起来就是个 SplitButton。此方法用以实现类似 SplitButton 的效果。
        /// </summary>
        private void btnFindHeaders_AppendArrow_Initialized(object sender, EventArgs e)
        {
            //SplitterButton的弹出菜单要处理一下，左键单显示，右键不显示
            splitMenu2.PlacementTarget = btnFindHeaders_AppendArrow;
            btnFindHeaders_AppendArrow.ContextMenu = null;
        }

        /// <summary>
        /// 主界面“查找任务列表”按钮右侧边附加的小三角按钮，点击会弹出一个快捷菜单，可以用来查找指定类型的任务列表。
        /// 这两个按钮看起来就是个 SplitButton。
        /// </summary>
        private void btnFindTaskListItems_AppendArrow_Click(object sender, RoutedEventArgs e)
        {
            splitMenu.HorizontalOffset = -btnFindTaskListItems.ActualWidth;
            splitMenu.PlacementTarget = btnFindTaskListItems_AppendArrow;
            splitMenu.Placement = PlacementMode.Bottom;
            splitMenu.IsOpen = true;
        }

        /// <summary>
        /// 主界面“查找标题”按钮右侧边附加的小三角按钮，点击会弹出一个快捷菜单，可以用来查找指定级别的标题。
        /// 这两个按钮看起来就是个 SplitButton。
        /// </summary>
        private void btnFindHeaders_AppendArrow_Click(object sender, RoutedEventArgs e)
        {
            splitMenu2.HorizontalOffset = -btnFindHeaders.ActualWidth;
            splitMenu2.PlacementTarget = btnFindHeaders_AppendArrow;
            splitMenu2.Placement = PlacementMode.Bottom;
            splitMenu2.IsOpen = true;
        }

        /// <summary>
        /// 用“region { } region”块包围当前选定的文本行。
        /// </summary>
        private void miWrapWithRegionMark_Click(object sender, RoutedEventArgs e)
        {
            var editor = ActivedEditor;

            if (editor.EditorBase.SelectionLength == 0)
            {
                editor.EditorBase.SelectedText = "\r\n\r\nregion { \r\n\r\n\r\n\r\n} region\r\n\r\n";
                var destSel = editor.EditorBase.SelectionStart + 13;
                if (destSel < editor.EditorBase.Document.TextLength)
                {
                    editor.EditorBase.Select(destSel, 0);
                }
                return;
            }

            var fstLine = editor.EditorBase.Document.GetLineByOffset(editor.EditorBase.SelectionStart);
            var lastLine = editor.EditorBase.Document.GetLineByOffset(editor.EditorBase.SelectionStart + editor.EditorBase.SelectionLength);

            editor.EditorBase.Document.Insert(lastLine.EndOffset, "\r\n\r\n} region\r\n\r\n");
            editor.EditorBase.Document.Insert(fstLine.Offset, "\r\nregion { \r\n\r\n");
            var destSel2 = fstLine.Offset + 11;
            if (destSel2 < editor.EditorBase.Document.TextLength)
            {
                editor.EditorBase.Select(destSel2, 0);
            }
        }

        /// <summary>
        /// 打印预览当前活动编辑器。
        /// </summary>
        private void miPrintPreview_Click(object sender, RoutedEventArgs e)
        {
            PrintPreview();
        }

        /// <summary>
        /// 打印当前活动编辑器。
        /// </summary>
        private void miPrintDocument_Click(object sender, RoutedEventArgs e)
        {
            PrintDocument();
        }

        /// <summary>
        /// 对当前活动编辑器执行“打印预览”。
        /// </summary>
        public void PrintPreview()
        {
            var editor = ActivedEditor;
            if (editor == null) return;

            if (editor.EditorBase.PageSetupDialog())              // .NET dialog
            {
                editor.EditorBase.PrintPreviewDialog(editor.ShortFileName);           // WPF print preview dialog
            }
        }

        /// <summary>
        /// 打印当前活动编辑器。
        /// </summary>
        public void PrintDocument()
        {
            var editor = ActivedEditor;
            if (editor == null) return;

            if (editor.EditorBase.PageSetupDialog())              // .NET dialog
            {
                editor.EditorBase.PrintDialog(editor.ShortFileName);           // WPF print preview dialog
            }

            //调用方法示例，勿删。
            //editor.EditorBase.PrintPreviewDialog(filename);   // WPF print preview dialog, filename as document title
            //editor.EditorBase.PrintDialog();                   // WPF print dialog
            //editor.EditorBase.PrintDialog(filename);          // WPF print dialog, filename as document title
            //editor.EditorBase.PrintDirect();                   // prints to default or previously selected printer
            //editor.EditorBase.PrintDirect(filename)           // prints to default or previously selected printer, filename as document title
        }

        /// <summary>
        /// 查找功能在启用“正则表达式”时，禁止使用“通配符”。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cbRegex_Checked(object sender, RoutedEventArgs e)
        {
            cbWildcards.IsChecked = false;
        }

        /// <summary>
        /// 查找功能在启用“通配符”时，禁止使用“正则表达式”。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cbWildcards_Checked(object sender, RoutedEventArgs e)
        {
            cbRegex.IsChecked = false;
        }

        /// <summary>
        /// 开关“AutoNumberHeaders”。开启后编译的 Html 文档中所有“标题<h1></h1>...<h6></h6>”都会自动添加序号。
        /// </summary>
        private void miAutoNumberHeaders_Click(object sender, RoutedEventArgs e)
        {
            miAutoNumberHeaders.IsChecked = !miAutoNumberHeaders.IsChecked;
            this.AutoNumberHeaders = miAutoNumberHeaders.IsChecked;
            this.configManager.Set("AutoNumberHeaders", miAutoNumberHeaders.IsChecked.ToString());
        }

        /// <summary>
        /// 根据主界面“查找范围”框中定义的范围，查找所有六个级别的标题。
        /// </summary>
        private void btnFindHeaders_Click(object sender, RoutedEventArgs e)
        {
            string keyWord = @"^ {0,3}#{1,6}.*$";
            FindHeaders(keyWord, tvFindAndReplace);
        }

        /// <summary>
        /// 根据主界面“查找范围”框中定义的范围，查找所有六个级别的标题。须指定用于查找标题的正则表达式。
        /// </summary>
        /// <param name="keyWord">要用于查找标题的正则表达式。</param>
        /// <param name="treeView">指定要将查找结果输出到哪个树型框。</param>
        private void FindHeaders(string keyWord, TreeView treeView)
        {
            FindText(keyWord, (cmbSearchArea.SelectedItem as ComboBoxItem).Tag.ToString(), true, treeView);

            if (treeView.HasItems == false)
            {
                treeView.Items.Add(new TextBlock() { Text = "<...未找到结果...>", });
            }

            //防止右工具栏被折叠
            if (treeView == tvFindAndReplace)
            {
                if (rightToolBar.SelectedIndex != 1)
                {
                    rightToolBar.SelectedIndex = 1;
                }

                if (cdRightToolsArea.ActualWidth < 140)
                {
                    cdLeftToolsArea.Width = new GridLength(2, GridUnitType.Star);
                }
            }
        }

        /// <summary>
        /// 根据主界面“查找范围”框中定义的范围，查找所有以“#”开头的标题。
        /// </summary>
        private void miSearchH1_Click(object sender, RoutedEventArgs e)
        {
            FindHeaders(@"^ {0,3}#[^#].*$", tvFindAndReplace);
        }

        /// <summary>
        /// 根据主界面“查找范围”框中定义的范围，查找所有以“##”开头的标题。
        /// </summary>
        private void miSearchH2_Click(object sender, RoutedEventArgs e)
        {
            FindHeaders(@"^ {0,3}##[^#].*$", tvFindAndReplace);
        }

        /// <summary>
        /// 根据主界面“查找范围”框中定义的范围，查找所有以“###”开头的标题。
        /// </summary>
        private void miSearchH3_Click(object sender, RoutedEventArgs e)
        {
            FindHeaders(@"^ {0,3}###[^#].*$", tvFindAndReplace);
        }

        /// <summary>
        /// 根据主界面“查找范围”框中定义的范围，查找所有以“####”开头的标题。
        /// </summary>
        private void miSearchH4_Click(object sender, RoutedEventArgs e)
        {
            FindHeaders(@"^ {0,3}####[^#].*$", tvFindAndReplace);
        }

        /// <summary>
        /// 根据主界面“查找范围”框中定义的范围，查找所有以“#####”开头的标题。
        /// </summary>
        private void miSearchH5_Click(object sender, RoutedEventArgs e)
        {
            FindHeaders(@"^ {0,3}#####[^#].*$", tvFindAndReplace);
        }

        /// <summary>
        /// 根据主界面“查找范围”框中定义的范围，查找所有以“######”开头的标题。
        /// </summary>
        private void miSearchH6_Click(object sender, RoutedEventArgs e)
        {
            FindHeaders(@"^ {0,3}######.*$", tvFindAndReplace);
        }

        /// <summary>
        /// 根据主界面“查找范围”框中定义的范围，查找所有以“#”开头的标题。
        /// </summary>
        private void miSearchAllH_Click(object sender, RoutedEventArgs e)
        {
            FindHeaders(@"^ {0,3}#{1,6}.*$", tvFindAndReplace);
        }

        /// <summary>
        /// 打开 LME 教程（这是个 chm 文档）。
        /// </summary>
        private void btnBook_Click(object sender, RoutedEventArgs e)
        {
            var bookFullName = Globals.InstalledPath + "Lunar Markdown Editor 教程.chm";
            if (File.Exists(bookFullName))
            {
                System.Diagnostics.Process.Start(bookFullName);
            }
        }

        private List<EnToChEntry> dictionary = new List<EnToChEntry>();
        /// <summary>
        /// 英译中词典，用于智能提示功能。
        /// </summary>
        public List<EnToChEntry> Dictionary
        {
            get { return dictionary; }
        }

        /// <summary>
        /// 向英译中词典中添加词条。
        /// </summary>
        private void miAddEnToChEntry_Click(object sender, RoutedEventArgs e)
        {
            var window = new AddEnToChEntryWindow()
            {
                WindowStartupLocation = WindowStartupLocation.CenterOwner,
                Owner = this,
            };
            window.EntryAdded += Window_EntryAdded;
            window.Show();
        }

        /// <summary>
        /// 自动完成提示窗口中添加词条时触发此方法。
        /// </summary>
        private void Window_EntryAdded(object sender, EntryAddedEventArgs e)
        {
            dictionary.Add(e.Entry);
            foreach (var i in this.mainTabControl.Items)
            {
                var edit = i as MarkdownEditor;
                if (edit == null) continue;

                edit.EditorBase.AddEntryToCompletionWindow(e.Entry);
            }
        }

        /// <summary>
        /// 编辑英译中词典中的“用户词典”。
        /// </summary>
        private void miEditEnToChUserDictonary_Click(object sender, RoutedEventArgs e)
        {
            var result = EditDictionary(Globals.PathOfUserDictionary, Globals.AppName + " - 编辑用户词典");
            if (result != string.Empty)
            {
                LMessageBox.Show("不能编辑用户词典。错误消息如下：\r\n" + result,
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
            else
            {
                LoadDictionary();
            }
        }

        /// <summary>
        /// 编辑英译中词典中的“工作区词典”。
        /// </summary>
        private void miEditEnToChWorkspaceDictionary_Click(object sender, RoutedEventArgs e)
        {
            var result = EditDictionary(Globals.PathOfWorkspaceDictionary, Globals.AppName + " - 编辑工作区词典");
            if (result != string.Empty)
            {
                LMessageBox.Show("不能编辑工作区词典。错误消息如下：\r\n" + result,
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
            else
            {
                LoadDictionary();
            }
        }

        /// <summary>
        /// 编辑英译中词典文件。
        /// </summary>
        /// <param name="filePath">要编辑的词典文件的完整路径。</param>
        /// <param name="editorWindowTitle">编辑器窗口标题文本。</param>
        /// <returns>返回 string.Empty 表示一切正常完成。</returns>
        private string EditDictionary(string filePath, string editorWindowTitle = null)
        {
            try
            {
                if (File.Exists(filePath) == false)
                {
                    File.WriteAllText(filePath, "; 英文\t中文\t字母缩写\t注释\r\n", Encoding.UTF8);
                }

                if (File.Exists(filePath) == false)
                {
                    return "未找到文件且无法创建。";
                }

                //编辑词典文件
                var dictionaryEditor = new DictionaryEditor(filePath)
                {
                    Owner = this,
                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
                };

                if (string.IsNullOrWhiteSpace(editorWindowTitle))
                {
                    dictionaryEditor.Title = Globals.AppName;
                }
                else { dictionaryEditor.Title = editorWindowTitle; }

                dictionaryEditor.ShowDialog();

                #region 旧版，以纯文本形式编辑，不够直观。
                //var pw = new PlainTextEditor(filePath)
                //{
                //    Owner = this,
                //    WindowStartupLocation = WindowStartupLocation.CenterOwner,
                //    Title = Globals.AppName + " - 编辑词典",
                //};

                //using (StreamReader sr = new StreamReader(filePath))
                //{
                //    var content = sr.ReadToEnd();
                //    string fstLine = "";
                //    int index = content.IndexOf("\r\n");

                //    if (index >= 0)
                //    {
                //        fstLine = content.Substring(0, index - 2);
                //    }

                //    if (fstLine.StartsWith(";格式说明：") == false)
                //    {
                //        content = ";格式说明： 英文[Tab]中文[Tab]字母缩写[Tab]注释\r\n" + content;
                //    }

                //    pw.tbx.Text = content;
                //}

                //pw.ShowDialog(); 
                #endregion
                return string.Empty;
            }
            catch (Exception ex)
            {
                return ex.Message + "\r\n" + ex.StackTrace;
            }
        }

        /// <summary>
        /// 开关“自动提示译词”功能。
        /// </summary>
        private void miIsEnToChineseDictEnabled_Click(object sender, RoutedEventArgs e)
        {
            if (this.IsAutoCompletionEnabled == false)
            {
                var result = LMessageBox.Show("需要打开【自动完成】，要继续吗？", Globals.AppName,
                     MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (result != MessageBoxResult.Yes) return;

                miIsAutoCompletionEnabled.IsChecked = true;
                this.IsAutoCompletionEnabled = true;
                this.configManager.Set("IsAutoCompletionEnabled", true.ToString());
            }

            miIsEnToChineseDictEnabled.IsChecked = !miIsEnToChineseDictEnabled.IsChecked;
            this.IsEnToChineseDictEnabled = miIsEnToChineseDictEnabled.IsChecked;
            this.configManager.Set("IsEnToChineseDictEnabled", miIsEnToChineseDictEnabled.IsChecked.ToString());
        }

        /// <summary>
        /// 整理活动编辑器中的空段落，将过多的空段合并。
        /// </summary>
        private void miFormatParagraphs_Click(object sender, RoutedEventArgs e)
        {
            var edit = this.ActivedEditor;
            if (edit == null) return;

            cbRegex.IsChecked = true;
            cmbFindText.Text = @"(\r\n){2,}";
            cmbReplaceTextInputBox.Text = @"\r\n";
            cmbSearchArea.SelectedIndex = 0;
            btnReplaceAll_Click(sender, e);
        }

        /// <summary>
        /// 折叠左工具栏。
        /// </summary>
        private void btnCollapseLeftToolArea_Click(object sender, RoutedEventArgs e)
        {
            SwitchLeftToolBarToggle();
        }

        /// <summary>
        /// 折叠右工具栏。
        /// </summary>
        private void btnCollapseRightToolArea_Click(object sender, RoutedEventArgs e)
        {
            SwitchRightToolBarToggle();
        }

        /// <summary>
        /// 折叠“查找/替换”面板。
        /// </summary>
        private void btnCollapseFindAndReplacePanel_Click(object sender, RoutedEventArgs e)
        {
            SwitchFindAndReplacePanelToggle();
        }

        /// <summary>
        /// 切换 FormatAfterCompile 属性的值。该属性用于决定是否对编译好的 Html 文件进行格式化。
        /// **对 Html 的格式化是用 NSoup 实现的。
        /// </summary>
        private void miFormatAfterCompile_Click(object sender, RoutedEventArgs e)
        {
            this.FormatAfterCompile =
            miFormatAfterCompile.IsChecked = !miFormatAfterCompile.IsChecked;
            this.ConfigManager.Set("FormatAfterCompile", miFormatAfterCompile.IsChecked.ToString());
        }

        /// <summary>
        /// 查找所有 TODO Comment，查找结果显示在右工具栏“查找结果”页面。
        /// </summary>
        private void miSearchAllTodoComment_Click(object sender, RoutedEventArgs e)
        {
            tvTaskList.Items.Clear();
            FindTodoComment((cmbSearchArea.SelectedItem as ComboBoxItem).Tag.ToString(), tvTaskList);

            if (tvTaskList.Items.Count <= 0)
            {
                tvTaskList.Items.Add(new TreeViewItem() { Header = "<没找到 TODO 注释...>", });
            }

            //防止右工具栏被隐藏。
            if (rightToolBar.SelectedIndex != 3)
            {
                rightToolBar.SelectedIndex = 3;
            }

            if (cdRightToolsArea.ActualWidth < 140)
            {
                cdLeftToolsArea.Width = new GridLength(3, GridUnitType.Star);
            }
        }

        /// <summary>
        /// 根据指定的查找范围来查找所有 TODO Comment。
        /// </summary>
        /// <param name="searchArea">查找范围：包括“ActiveDocument”、“OpenedDocuments”、“AllFiles”。</param>
        /// <param name="treeView">指定要将查找到的结果放在哪个 TreeView 中。</param>
        private void FindTodoComment(string searchArea, TreeView treeView)
        {
            if (treeView == null) treeView = tvTaskList;

            treeView.Items.Clear();
            switch (searchArea)
            {
                case "ActiveDocument":
                    {
                        FindTodoCommentsInActiveDocuments(treeView);
                        break;
                    }
                case "OpenedDocuments":
                    {
                        FindTodoCommentsInOpenedDocuments(treeView);
                        break;
                    }
                case "AllFiles":
                    {
                        //这个需要用到递归
                        FindTodoCommentInAllFiles(Globals.PathOfWorkspace, treeView);
                        break;
                    }
            }
        }

        /// <summary>
        /// 在所有打开的文档中查找 TODO Comments。
        /// </summary>
        /// <param name="treeView">指定要将查找到的结果放在哪个 TreeView 中。</param>
        private void FindTodoCommentsInOpenedDocuments(TreeView treeView)
        {
            foreach (var item in this.mainTabControl.Items)
            {
                var efi = item as MarkdownEditor;
                if (efi != null)
                {
                    FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(efi.FullFilePath, efi.ShortFileName);
                    var lines = efi.EditorBase.Document.Lines;
                    for (int i = 0; i < lines.Count; i++)
                    {
                        var line = lines[i];
                        var lineText = efi.EditorBase.Document.GetText(line.Offset, line.Length);
                        if (string.IsNullOrEmpty(lineText)) continue;

                        string todoCommentMark;
                        string todoCommentTail;
                        if (CustomMarkdownSupport.IsTodoCommentLine(lineText, out todoCommentMark, out todoCommentTail))
                        {
                            FindLineTreeViewItem newItem = new FindLineTreeViewItem(efi.FullFilePath, efi.ShortFileName, line.LineNumber,
                                todoCommentMark.Length, lineText.Length - todoCommentMark.Length, lineText, Brushes.Red, FindLineTreeViewItem.ItemType.TodoComment);
                            fdi.Items.Add(newItem);
                        }
                    }

                    if (fdi.Items.Count > 0)
                    {
                        treeView.Items.Add(fdi);
                        fdi.IsExpanded = true;
                        (fdi.Items[0] as TreeViewItem).IsSelected = true;
                    }
                }
            }
        }

        /// <summary>
        /// 在活动文档中查找所有 TODO Comments。
        /// </summary>
        /// <param name="treeView">指定要将查找到的结果放在哪个 TreeView 中。</param>
        private void FindTodoCommentsInActiveDocuments(TreeView treeView)
        {
            var efi = this.mainTabControl.SelectedItem as MarkdownEditor;
            if (efi != null)
            {
                FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(efi.FullFilePath, efi.ShortFileName);
                var lines = efi.EditorBase.Document.Lines;
                for (int i = 0; i < lines.Count; i++)
                {
                    var line = lines[i];
                    var lineText = efi.EditorBase.Document.GetText(line.Offset, line.Length);
                    if (string.IsNullOrEmpty(lineText)) continue;

                    string todoCommentMark;
                    string todoCommentTail;
                    if (CustomMarkdownSupport.IsTodoCommentLine(lineText, out todoCommentMark, out todoCommentTail))
                    {
                        FindLineTreeViewItem newItem = new FindLineTreeViewItem(efi.FullFilePath, efi.ShortFileName, line.LineNumber,
                            todoCommentMark.Length, lineText.Length - todoCommentMark.Length, lineText, Brushes.Red, FindLineTreeViewItem.ItemType.TodoComment);
                        fdi.Items.Add(newItem);
                    }
                }

                if (fdi.Items.Count > 0)
                {
                    treeView.Items.Add(fdi);
                    fdi.IsExpanded = true;
                    (fdi.Items[0] as TreeViewItem).IsSelected = true;
                }
            }
        }

        /// <summary>
        /// 从指定目录开始查找能找到的所有 Markdown 文件（含子目录中的文件）中的 TODO Comment。
        /// </summary>
        /// <param name="directoryPath">开始查找的目录全路径。</param>
        /// <param name="treeView">指定将查找结果输出到哪个树型框。</param>
        private void FindTodoCommentInAllFiles(string directoryPath, TreeView treeView)
        {
            if (Directory.Exists(directoryPath) == false) return;
            var directory = new DirectoryInfo(directoryPath);

            //先处理当前目录下的文件
            var childFilesInfos = directory.GetFiles();

            foreach (var childFileInfo in childFilesInfos)
            {
                if (childFileInfo.FullName.ToLower().EndsWith(".md") == false) continue;

                string[] lines;

                //已打开的文件，按打开的情况算，没打开的，按磁盘文本查找。
                var fileEditor = GetOpenedEditor(childFileInfo.FullName);
                if (fileEditor != null)
                {
                    lines = fileEditor.EditorBase.Text.Replace("\r", "").Split(new char[] { '\n' });
                }
                else
                {
                    lines = File.ReadAllLines(childFileInfo.FullName);
                }

                FindDocumentTreeViewItem fdi = new FindDocumentTreeViewItem(childFileInfo.FullName, childFileInfo.Name);

                var lineNum = 0;
                for (int i = 0; i < lines.Length; i++)
                {
                    lineNum++;

                    var lineText = lines[i];
                    if (string.IsNullOrEmpty(lineText)) continue;

                    string todoCommentMark;
                    string todoCommentTail;
                    if (CustomMarkdownSupport.IsTodoCommentLine(lineText, out todoCommentMark, out todoCommentTail))
                    {
                        FindLineTreeViewItem newItem = new FindLineTreeViewItem(childFileInfo.FullName,
                            childFileInfo.Name, lineNum, todoCommentMark.Length, lineText.Length - todoCommentMark.Length,
                            lineText, Brushes.Red, FindLineTreeViewItem.ItemType.TodoComment);
                        fdi.Items.Add(newItem);
                    }
                }

                if (fdi.Items.Count > 0)
                {
                    treeView.Items.Add(fdi);
                    fdi.IsExpanded = true;
                }
            }

            //再递归处理子目录下的文件
            var childDirectories = directory.GetDirectories();
            foreach (var childDirectory in childDirectories)
            {
                FindTodoCommentInAllFiles(childDirectory.FullName, treeView);
            }
        }

        /// <summary>
        /// 折叠左工具栏。
        /// </summary>
        private void btnCollapseLeftTools_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (rdLeftTools.ActualHeight <= 40)
            {
                rdLeftTools.Height = new GridLength(1, GridUnitType.Star);
            }
            else rdLeftTools.Height = new GridLength(0);
        }

        /// <summary>
        /// 手工指定（设置）用户自行下载、安装的微软公司 Html Help Workshop 应用程序的磁盘位置。
        /// </summary>
        private void miSetHtmlHelpWorkshopFullName_Click(object sender, RoutedEventArgs e)
        {
            SetHtmlHelpWorkshopFullName();
        }

        /// <summary>
        /// 在工作区中当前选定的目录的父目录中创建 Markdown 文件。
        /// 目录较长时，再回去找父目录会比较烦人。
        /// </summary>
        private void miNewSameLevelFile_Click(object sender, RoutedEventArgs e)
        {
            var selItem = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (selItem == null)
            {
                LMessageBox.Show("请先在工作区选择一个条目作为创建文件的位置。", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (selItem.IsResourceDirectory || selItem.IsResourceFile)
            {
                LMessageBox.Show("资源文件夹或资源文件附近不能创建 Markdown 文件。", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (selItem.IsDirectoryExists)
            {
                var parentItem = selItem.Parent as WorkspaceTreeViewItem;
                if (parentItem == null)
                {
                    LMessageBox.Show("找不到当前位置的上级，无法确定同级文档应该创建在什么位置。。", Globals.AppName,
                         MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                parentItem.IsSelected = true;
                NewFile(false);
            }
            else if (selItem.IsMarkdownFilePath)
            {
                NewFile(false);
            }
        }

        /// <summary>
        /// 调用 Windows 系统中安装的 Lunar Concept 绘制概念图。
        /// </summary>
        private void miInsertConcept_Click(object sender, RoutedEventArgs e)
        {
            //尝试从注册表中读取安装的hhw.exe的路径
            RegistryKey regSubKey;
            RegistryKey regKey = Registry.LocalMachine;
            string strRegPath = @"SOFTWARE\Lunar Concept";
            regSubKey = regKey.OpenSubKey(strRegPath);
            if (regSubKey == null)
            {
                LMessageBox.Show("未能在此计算机上找到 Lunar Concept，无法调用它来绘制概念图。", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            string lunarConceptInstalledPath = regSubKey.GetValue("InstalledPath").ToString();

            if (File.Exists(lunarConceptInstalledPath) == false)
            {
                LMessageBox.Show("此计算机上未安装 Lunar Concept，无法调用它来绘制概念图。", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            string imageResourceFolderPath;
            var selItem = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (selItem == null)
            {
                LMessageBox.Show("请在左侧工作区管理器中选择一个“Images~”资源文件夹作为添加概念图文件的位置。", Globals.AppName,
                         MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (selItem.IsMarkdownFilePath)
            {
                var filePath = selItem.FullPath;
                imageResourceFolderPath = selItem.FullPath.Substring(0, selItem.FullPath.Length - 3) + "~\\Images~\\";
            }
            else if (selItem.IsImageResourceDirectory)
            {
                imageResourceFolderPath = selItem.FullPath;
            }
            else if (selItem.IsDirectoryExists)
            {
                var directoryInfo = new DirectoryInfo(selItem.FullPath);
                var metaFileImageResourceDirectoryPath =
                    (directoryInfo.FullName.EndsWith("\\") ? directoryInfo.FullName : (directoryInfo.FullName + "\\")) +
                    $"_{directoryInfo.Name}~\\Images~\\";
                imageResourceFolderPath = metaFileImageResourceDirectoryPath;
            }
            else
            {
                LMessageBox.Show("请在左侧工作区管理器中选择一个“Images~”资源文件夹作为添加概念图文件的位置。", Globals.AppName,
                           MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (Directory.Exists(imageResourceFolderPath) == false)
            {
                Directory.CreateDirectory(imageResourceFolderPath);
                WorkspaceManager.Refresh(selItem.FullPath);
            }

            try
            {
                var shortName = InputBox.Show(Globals.AppName, "请输入概念图短名：", "概念图", true, "不需要后缀名。", false);
                if (string.IsNullOrWhiteSpace(shortName)) return;

                if (shortName.ToLower().EndsWith(".png") == false)
                {
                    shortName += ".png";
                }

                var destFileName = imageResourceFolderPath + shortName;
                File.Copy(Globals.InstalledPath + "ConceptExample.png", destFileName);

                //在资源管理器界面进行刷新。
                Globals.MainWindow.WorkspaceManager.Refresh(destFileName);

                System.Diagnostics.Process.Start(lunarConceptInstalledPath, $"\"{destFileName}\"");
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 编辑由 Lunar Concept 生成的 png 格式的概念图。
        /// Lunar Concept 生成的 png 图形末尾被附加了一个纯文本的 XML 文件的所有内容，所以是可以编辑的。
        /// </summary>
        private void miEditConcept_Click(object sender, RoutedEventArgs e)
        {
            var selItem = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (selItem == null || selItem.IsImageFileExist == false)
            {
                LMessageBox.Show("　　请在左侧工作区管理器中选择一个“png”资源文件（该资源文件必须是由 Lunar Concept 生成的才行）。", Globals.AppName,
                           MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            //尝试从注册表中读取安装的hhw.exe的路径
            RegistryKey regSubKey;
            RegistryKey regKey = Registry.LocalMachine;
            string strRegPath = @"SOFTWARE\Lunar Concept";
            regSubKey = regKey.OpenSubKey(strRegPath);
            if (regSubKey == null)
            {
                LMessageBox.Show("未能在此计算机上找到 Lunar Concept，无法调用它来绘制概念图。", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }
            else
            {
                string lunarConceptInstalledPath = regSubKey.GetValue("InstalledPath").ToString();
                System.Diagnostics.Process.Start(lunarConceptInstalledPath, $"\"{selItem.FullPath}\"");
            }
        }

        /// <summary>
        /// 调用 Windows 系统中安装的 Lunar Mind 绘制概念图。
        /// </summary>
        private void miInsertMindMap_Click(object sender, RoutedEventArgs e)
        {
            //尝试从注册表中读取安装的hhw.exe的路径
            RegistryKey regSubKey;
            RegistryKey regKey = Registry.LocalMachine;
            string strRegPath = @"SOFTWARE\Lunar Mind";
            regSubKey = regKey.OpenSubKey(strRegPath);
            if (regSubKey == null)
            {
                LMessageBox.Show("未能在此计算机上找到 Lunar Mind，无法调用它来绘制思维导图。", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            string lunarConceptInstalledPath = regSubKey.GetValue("InstalledPath").ToString();

            if (File.Exists(lunarConceptInstalledPath) == false)
            {
                LMessageBox.Show("此计算机上未安装 Lunar Mind，无法调用它来绘制思维导图。", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            string imageResourceFolderPath;
            var selItem = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (selItem == null)
            {
                LMessageBox.Show("请在左侧工作区管理器中选择一个“Images~”资源文件夹作为添加概念图文件的位置。", Globals.AppName,
                         MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (selItem.IsMarkdownFilePath)
            {
                var filePath = selItem.FullPath;
                imageResourceFolderPath = selItem.FullPath.Substring(0, selItem.FullPath.Length - 3) + "~\\Images~\\";
            }
            else if (selItem.IsImageResourceDirectory)
            {
                imageResourceFolderPath = selItem.FullPath;
            }
            else if (selItem.IsDirectoryExists)
            {
                var directoryInfo = new DirectoryInfo(selItem.FullPath);
                var metaFileImageResourceDirectoryPath =
                    (directoryInfo.FullName.EndsWith("\\") ? directoryInfo.FullName : (directoryInfo.FullName + "\\")) +
                    $"_{directoryInfo.Name}~\\Images~\\";
                imageResourceFolderPath = metaFileImageResourceDirectoryPath;
            }
            else
            {
                LMessageBox.Show("请在左侧工作区管理器中选择一个“Images~”资源文件夹作为添加概念图文件的位置。", Globals.AppName,
                           MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (Directory.Exists(imageResourceFolderPath) == false)
            {
                Directory.CreateDirectory(imageResourceFolderPath);
                WorkspaceManager.Refresh(selItem.FullPath);
            }

            try
            {
                var shortName = InputBox.Show(Globals.AppName, "请输入思维导图短名：", "思维导图", true, "不需要后缀名。", false);
                if (string.IsNullOrWhiteSpace(shortName)) return;

                if (shortName.ToLower().EndsWith(".png") == false)
                {
                    shortName += ".png";
                }

                var destFileName = imageResourceFolderPath + shortName;
                File.Copy(Globals.InstalledPath + "MindMapExample.png", destFileName);

                //在资源管理器界面进行刷新。
                Globals.MainWindow.WorkspaceManager.Refresh(destFileName);

                System.Diagnostics.Process.Start(lunarConceptInstalledPath, $"\"{destFileName}\"");
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 编辑由 Lunar Concept 生成的 png 格式的概念图。
        /// Lunar Concept 生成的 png 图形末尾被附加了一个纯文本的 XML 文件的所有内容，所以是可以编辑的。
        /// </summary>
        private void miEditMindMap_Click(object sender, RoutedEventArgs e)
        {
            var selItem = tvWorkDirectory.SelectedItem as WorkspaceTreeViewItem;
            if (selItem == null || selItem.IsImageFileExist == false)
            {
                LMessageBox.Show("　　请在左侧工作区管理器中选择一个“png”资源文件（该资源文件必须是由 Lunar Map 生成的才行）。", Globals.AppName,
                           MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            //尝试从注册表中读取安装的LunarMind.exe的路径
            RegistryKey regSubKey;
            RegistryKey regKey = Registry.LocalMachine;
            string strRegPath = @"SOFTWARE\Lunar Mind";
            regSubKey = regKey.OpenSubKey(strRegPath);
            if (regSubKey == null)
            {
                LMessageBox.Show("未能在此计算机上找到 Lunar Mind，无法调用它来绘制思维导图。", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }
            else
            {
                string lunarMindInstalledPath = regSubKey.GetValue("InstalledPath").ToString();
                System.Diagnostics.Process.Start(lunarMindInstalledPath, $"\"{selItem.FullPath}\"");
            }
        }

        private void miShowSpaces_Click(object sender, RoutedEventArgs e)
        {
            miShowSpaces.IsChecked = !miShowSpaces.IsChecked;
            this.IsShowSpaces = miShowSpaces.IsChecked;
            this.configManager.Set("IsShowSpaces", miIsEnToChineseDictEnabled.IsChecked.ToString());
        }

        private void miShowEndOfLine_Click(object sender, RoutedEventArgs e)
        {
            miShowEndOfLine.IsChecked = !miShowEndOfLine.IsChecked;
            this.IsShowEndOfLine = miShowEndOfLine.IsChecked;
            this.configManager.Set("IsShowEndOfLine", miIsEnToChineseDictEnabled.IsChecked.ToString());
        }

        private void miShowTabs_Click(object sender, RoutedEventArgs e)
        {
            miShowTabs.IsChecked = !miShowTabs.IsChecked;
            this.IsShowTabs = miShowTabs.IsChecked;
            this.configManager.Set("IsShowTabs", miIsEnToChineseDictEnabled.IsChecked.ToString());
        }

        private void miShowTitleInWorkspaceManager_Click(object sender, RoutedEventArgs e)
        {
            SwitchShowTitleInWorkspaceManager();
        }

        private void ckxShowTitle_Click(object sender, RoutedEventArgs e)
        {
            SwitchShowTitleInWorkspaceManager();
        }

        private void SwitchShowTitleInWorkspaceManager()
        {
            ckxShowTitle.IsChecked =
            this.miShowTitleInWorkspaceManager.IsChecked =
            this.ShowTitleInWorkspaceManager = !this.ShowTitleInWorkspaceManager;
            this.configManager.Set("ShowTitleInWorkspaceManager", this.ShowTitleInWorkspaceManager.ToString());
        }

        private void ckxShowTitle_Checked(object sender, RoutedEventArgs e)
        {
            ckxShowTitle.Foreground = Brushes.Blue;
            ckxShowTitle.FontWeight = FontWeights.Bold;
        }

        private void ckxShowTitle_Unchecked(object sender, RoutedEventArgs e)
        {
            ckxShowTitle.FontWeight = FontWeights.Normal;
            ckxShowTitle.Foreground = Brushes.Black;
        }
    }
}